diff --git a/src/scene/mod.rs b/src/scene/mod.rs index f3dcb0730..ae75f5805 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -789,12 +789,16 @@ pub struct Scene { sdf_text_cache: RefCell< HashMap>>, >, - /// `(geometry_epoch, gathered text)` of the most recent SDF-text build. When a - /// new content id misses the cache but the journal shows no text-bearing - /// entity changed since this epoch, the same glyphs are reused instead of - /// re-walking every wire — an edit on plain geometry never rebuilds the text. + /// Per wire-source `(geometry_epoch, gathered text)` of the most recent + /// SDF-text build. When a new content id misses the cache but the journal + /// shows no text-bearing entity changed since that epoch, the same glyphs + /// are reused instead of re-walking every wire — an edit on plain geometry + /// never rebuilds the text. Keyed by the render source (sheet / tile / + /// content viewport / implicit view): a paper frame gathers several wire + /// sets in one frame, and a single slot handed the sheet's glyphs to the + /// content viewports, hiding every model-space text entity (#403). last_sdf_text: RefCell< - Option<(u64, std::sync::Arc>)>, + HashMap>)>, >, /// pane_grid layout tree for the Model tab — the source of truth for the /// tile split layout, resize and focus. `model_tiles` (the renderer's @@ -1115,7 +1119,7 @@ impl Scene { }]), active_model_tile: std::cell::Cell::new(0), sdf_text_cache: RefCell::new(HashMap::default()), - last_sdf_text: RefCell::new(None), + last_sdf_text: RefCell::new(HashMap::default()), // One pane mapped to tile 0 — matches the single default tile above. model_panes: iced::widget::pane_grid::State::new(0).0, selection: Rc::new(RefCell::new(SelectionState::default())), @@ -1394,26 +1398,39 @@ impl Scene { /// True when no text-bearing entity changed since `last_epoch`, so cached SDF /// glyphs stay valid. Text comes from Text / MText / Dimension / MultiLeader / - /// Leader / Table / attributes and from block references (their baked text - /// moves with the instance) — an edit to any of those, or any removal, - /// invalidates it; a plain line / arc / polyline edit does not. + /// Leader / Table / Tolerance / attributes (incl. ATTDEF) and from block + /// references (their baked text moves with the instance) — an edit to any of + /// those, or any removal, invalidates it; a plain line / arc / polyline edit + /// does not. fn text_unchanged(&self, last_epoch: u64) -> bool { match self.replay_since(last_epoch) { Some(deltas) => deltas.iter().all(|&(h, k)| { - k != ChangeKind::Removed - && !matches!( - self.document.get_entity(h), - Some( - EntityType::Text(_) - | EntityType::MText(_) - | EntityType::Dimension(_) - | EntityType::MultiLeader(_) - | EntityType::Leader(_) - | EntityType::Table(_) - | EntityType::AttributeEntity(_) - | EntityType::Insert(_) - ) - ) + if k == ChangeKind::Removed { + return false; + } + let Some(e) = self.document.get_entity(h) else { + return true; + }; + if matches!( + e, + EntityType::Text(_) + | EntityType::MText(_) + | EntityType::Dimension(_) + | EntityType::MultiLeader(_) + | EntityType::Leader(_) + | EntityType::Table(_) + | EntityType::Tolerance(_) + | EntityType::AttributeEntity(_) + | EntityType::AttributeDefinition(_) + | EntityType::Insert(_) + ) { + return false; + } + // Complex-linetype glyphs ride the host entity's own wire, so a + // plain line/polyline edit still moves text when its linetype + // embeds text or shapes. + let lt = crate::scene::view::render::linetype_name_for(&self.document, e); + crate::io::linetypes::resolve_complex_lt(&self.document, <).is_none() }), None => false, } diff --git a/src/scene/view/render.rs b/src/scene/view/render.rs index 0a24606ef..1a710f690 100644 --- a/src/scene/view/render.rs +++ b/src/scene/view/render.rs @@ -997,6 +997,7 @@ impl Scene { &self, wires: &[WireModel], wire_content_id: u64, + source_key: u64, ) -> std::sync::Arc> { use std::sync::Arc; { @@ -1007,11 +1008,13 @@ impl Scene { } // A new content id (every geometry edit) misses the cache — but if no // text-bearing entity changed since the last build, the glyphs are - // identical, so reuse them instead of re-walking every wire. + // identical, so reuse them instead of re-walking every wire. Reuse is + // per `source_key`: another source's glyphs are a different wire set, + // not an older build of this one (#403). { let reuse = { let last = self.last_sdf_text.borrow(); - match &*last { + match last.get(&source_key) { Some((epoch, arc)) if self.text_unchanged(*epoch) => Some(arc.clone()), _ => None, } @@ -1020,7 +1023,9 @@ impl Scene { self.sdf_text_cache .borrow_mut() .insert(wire_content_id, arc.clone()); - *self.last_sdf_text.borrow_mut() = Some((self.geometry_epoch, arc.clone())); + self.last_sdf_text + .borrow_mut() + .insert(source_key, (self.geometry_epoch, arc.clone())); return arc; } } @@ -1039,7 +1044,13 @@ impl Scene { } cache.insert(wire_content_id, verts.clone()); } - *self.last_sdf_text.borrow_mut() = Some((self.geometry_epoch, verts.clone())); + { + let mut last = self.last_sdf_text.borrow_mut(); + if last.len() > 8 { + last.clear(); + } + last.insert(source_key, (self.geometry_epoch, verts.clone())); + } verts } @@ -1344,7 +1355,22 @@ impl Scene { // internal text and the paper sheet's own annotation alike — each set // draws only the text that belongs to it. Cached on the wire content // id so an unchanged wire set is not re-walked every frame. - let text_verts = self.gather_text_verts(&all_wires, wire_content_id); + // The reuse fallback inside the gather is keyed per wire SOURCE — the + // sheet, a Model tile, a content viewport and the implicit view carry + // different glyph sets even at the same geometry epoch (#403). Tiles + // share the resident Model set, so they share one key; the implicit + // view mixes in the current layout block (BEDIT swaps sets without a + // geometry delta). + let text_source_key: u64 = if inst.tile_idx.is_some() { + 0x1000_0000_0000_0000 + } else if inst.paper_sheet { + 0x2000_0000_0000_0000 + } else if inst.handle == acadrust::Handle::NULL { + 0x4000_0000_0000_0000 | self.current_layout_block_handle().value() + } else { + 0x3000_0000_0000_0000 | inst.handle.value() + }; + let text_verts = self.gather_text_verts(&all_wires, wire_content_id, text_source_key); // Grip-drag / command-preview glyphs, excluded from the epoch-cached base // gather above. Two sources, both tiny (one operation's worth) and walked // per frame: the overlay wires' own glyphs (MOVE / COPY / ROTATE / SCALE /