diff --git a/ROADMAP.md b/ROADMAP.md index 48f8923cb..cb43e5957 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -132,6 +132,15 @@ handles; the render path re-tessellates only those, reusing the rest. Also useful on open: any partial cache (e.g. from block defns) can be re-used. +**Partial (landed): grip drag.** Dragging an entity's grip called +`scene.apply_grip` every move, which `bump_geometry`'d → a full model +re-tessellation per move (plus an O(N) clone of all wires for snapping). Now +the first move hides the edited entity from the base (one re-tess) and shows +it as a one-entity overlay preview; subsequent moves only re-tessellate that +one entity (cheap) — the base stays a cache hit. Snapping runs against the +set directly (the edited entity is already hidden, so no clone and no +self-snap). The drag commits on release / Esc: un-hide + one final re-tess. + **Partial (landed): preview / interim overlays.** `set_preview_wires`, `clear_preview_wire` and `set_interim_wire` used to `bump_geometry`, so every rubber-band frame of a drawing command re-tessellated the whole model (the diff --git a/src/app/mod.rs b/src/app/mod.rs index 80bd69065..8ac1d22e4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -231,6 +231,12 @@ pub(super) struct OpenCADStudio { /// being placed (follows the cursor). `(entity handle, new-arrow grip id)`. /// Esc before the placement click removes it again. grip_add_provisional: Option<(acadrust::Handle, usize)>, + /// Handle hidden from the base tessellation during an in-progress grip + /// drag. While dragging, the edited entity is excluded from the cached + /// wire set and shown as a cheap overlay preview instead, so each move + /// updates only the overlay rather than re-tessellating the whole model. + /// Committed (un-hidden + one re-tess) when the drag ends. `None` = idle. + grip_preview_handle: Option, /// Open Quick Select panel state. `None` = panel closed. Filters are /// applied via `Message::QSelectApply`; the panel is dismissed on /// Apply / Cancel / Esc / outside-click. @@ -1272,6 +1278,7 @@ impl OpenCADStudio { grip_popup: None, grip_pending: None, grip_add_provisional: None, + grip_preview_handle: None, qselect: None, show_ucs_icon: true, show_viewcube: true, diff --git a/src/app/update.rs b/src/app/update.rs index fb165dba2..48c3bff4b 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1313,6 +1313,14 @@ impl OpenCADStudio { self.tabs[i].scene.bump_geometry(); self.refresh_selected_grips(); } + // Commit/cleanup an in-progress grip drag: un-hide the + // edited entity, re-tessellate once, drop the preview. + if let Some(h) = self.grip_preview_handle.take() { + let i = self.active_tab; + self.tabs[i].scene.hidden.remove(&h); + self.tabs[i].scene.clear_preview_wire(); + self.tabs[i].scene.bump_geometry(); + } self.tabs[self.active_tab].snap_result = None; self.refresh_properties(); return Task::none(); @@ -2100,14 +2108,24 @@ impl OpenCADStudio { drop(cam); let raw = self.tabs[i].scene.paper_to_model(raw_paper); - let edited_name = grip.handle.value().to_string(); + // First move of this drag: hide the edited entity from the + // base tessellation (one re-tess) so subsequent moves only + // refresh a cheap overlay preview instead of re-tessellating + // the whole model on every move. + if self.grip_preview_handle != Some(grip.handle) { + if let Some(prev) = self.grip_preview_handle.take() { + self.tabs[i].scene.hidden.remove(&prev); + } + self.tabs[i].scene.hidden.insert(grip.handle); + self.tabs[i].scene.bump_geometry(); + self.grip_preview_handle = Some(grip.handle); + } + + // The edited entity is hidden, so it's already absent from + // `hit_test_wires` — snap against the set directly, no clone + // and no self-snap. let all_wires = self.tabs[i].scene.hit_test_wires(); - let snap_wires: Vec<_> = all_wires - .iter() - .filter(|w| w.name != edited_name) - .cloned() - .collect(); - let snap_hit = self.snapper.snap(raw, p, &snap_wires, vp_mat, bounds); + let snap_hit = self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds); let mut snapped = snap_hit.map(|s| s.world).unwrap_or(raw); self.tabs[i].snap_result = snap_hit; if let Some(s) = self.tabs[i].snap_result.as_mut() { @@ -2142,6 +2160,10 @@ impl OpenCADStudio { .apply_grip(grip.handle, grip.grip_id, apply); self.tabs[i].dirty = true; self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped; + // Overlay the moved entity (hidden from the base) — no base + // re-tessellation, just a one-entity preview tessellation. + let preview = self.tabs[i].scene.wire_models_for(&[grip.handle]); + self.tabs[i].scene.set_preview_wires(preview); self.refresh_selected_grips(); self.refresh_properties(); return Task::none(); @@ -2611,6 +2633,14 @@ impl OpenCADStudio { return Task::none(); } self.tabs[i].active_grip = None; + // Commit the grip drag: un-hide the edited entity and + // re-tessellate the base once with its final geometry, + // dropping the overlay preview. + if let Some(h) = self.grip_preview_handle.take() { + self.tabs[i].scene.hidden.remove(&h); + self.tabs[i].scene.clear_preview_wire(); + self.tabs[i].scene.bump_geometry(); + } // Placement confirmed — keep the just-added leader. self.grip_add_provisional = None; self.tabs[i].snap_result = None; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 5706d8f58..743203236 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -4890,7 +4890,10 @@ impl Scene { } _ => {} } - self.bump_geometry(); + // NOTE: no `bump_geometry()` here. The grip-drag caller hides the + // edited entity and previews it as an overlay during the drag (so a + // move doesn't re-tessellate the whole model), then bumps once on + // commit. Any other caller must bump geometry itself. } // ── Hit-test convenience: wire name → Handle ──────────────────────────