diff --git a/Cargo.lock b/Cargo.lock index 05a8f4b34..e44e802f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.0" -source = "git+https://github.com/HakanSeven12/acadrust?branch=main#f76e41adb47b09c66b3032d3727468e4303bcaf0" +source = "git+https://github.com/HakanSeven12/acadrust?branch=main#ceef045ed9fe4658dfb1ad488fe12f65c0311135" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 749334004..a7b4c1428 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -394,10 +394,12 @@ pub(super) fn on_open_file(&mut self) -> Task { // built the mesh caches above, so those caches contain none of // the xref'd geometry. The wire pass rebuilds from the document // each frame (bump_geometry covers it), but 3D-solid meshes are - // only tessellated by `populate_meshes_from_document` — re-run it - // so xref'd solids (walls, floors, roofs) actually render. (#203) + // only tessellated by populate — run the incremental variant so + // the already-cached host solids are kept and only the newly + // merged xref solids (walls, floors, roofs) are tessellated, + // avoiding a full re-tessellation of the whole drawing. (#203) if xref_merged { - self.tabs[i].scene.populate_meshes_from_document(); + self.tabs[i].scene.populate_missing_meshes_from_document(); } self.tabs[i].scene.selected = rustc_hash::FxHashSet::default(); self.tabs[i].scene.preview_wires = vec![]; diff --git a/src/scene/entity.rs b/src/scene/entity.rs index 599f56682..64a49531e 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -1209,8 +1209,28 @@ impl Scene { /// Called after loading a document or after undo/redo so that every /// `Solid3D` entity is represented in the mesh cache. pub fn populate_meshes_from_document(&mut self) { - self.meshes.clear(); - self.block_meshes.clear(); + self.populate_meshes_impl(false); + } + + /// Like [`populate_meshes_from_document`] but tessellates only solids + /// whose handle is not already cached — the existing meshes are kept. + /// + /// Used after an XREF merge: the host document's solids were already + /// tessellated by the background loader, and the merge assigns brand-new + /// handles to every imported xref entity (see `merge_xref_into_block`), + /// so cached handles are guaranteed to be host solids. This turns the + /// post-xref pass from "re-tessellate host + all xrefs" into "tessellate + /// only the newly merged xref solids" — the dominant cost when a drawing + /// attaches several large xrefs. (#203) + pub fn populate_missing_meshes_from_document(&mut self) { + self.populate_meshes_impl(true); + } + + fn populate_meshes_impl(&mut self, incremental: bool) { + if !incremental { + self.meshes.clear(); + self.block_meshes.clear(); + } // BLOCK-entity handles of the layout (model + paper) blocks. A solid // owned by one of these is top-level; anything else lives in a block // definition and is instanced per INSERT instead. (#123) @@ -1234,9 +1254,17 @@ impl Scene { .entities() .filter_map(|e| match e { EntityType::Solid3D(_) | EntityType::Region(_) | EntityType::Body(_) | EntityType::Surface(_) => { + let handle = e.common().handle; + // Incremental (post-xref) pass: leave already-tessellated + // host solids untouched, only build the newly merged ones. + if incremental + && (self.meshes.contains_key(&handle) || self.block_meshes.contains_key(&handle)) + { + return None; + } let color = self.render_style(e).0; let top_level = layout_blocks.contains(&e.common().owner_handle); - Some((e.common().handle, e.clone(), color, top_level)) + Some((handle, e.clone(), color, top_level)) } _ => None, })