perf(clipboard): derive the paste anchor from bounding boxes

COPYCLIP/CUTCLIP computed the clipboard anchor by tessellating every
selected entity and averaging all wire vertices — O(total geometry),
~250 ms when copying a whole drawing. The anchor only fixes which point
of the selection sits under the cursor at paste, so the mean of each
entity's bounding-box centre is an equally valid, far cheaper origin.
This commit is contained in:
sLuCHa 2026-07-08 18:57:11 +02:00
commit 56835791af
2 changed files with 25 additions and 25 deletions

View file

@ -70,8 +70,9 @@ impl OpenCADStudio {
.iter() .iter()
.filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned()) .filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned())
.collect(); .collect();
self.clipboard_centroid = super::super::helpers::entities_centroid( self.clipboard_centroid = super::super::helpers::entities_centroid_by_bbox(
&self.tabs[i].scene.wire_models_for(&handles), &self.tabs[i].scene.document,
&handles,
); );
self.clipboard = entities; self.clipboard = entities;
self.clipboard_deps = super::super::ClipboardDeps::capture( self.clipboard_deps = super::super::ClipboardDeps::capture(
@ -158,8 +159,9 @@ impl OpenCADStudio {
.iter() .iter()
.filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned()) .filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned())
.collect(); .collect();
self.clipboard_centroid = super::super::helpers::entities_centroid( self.clipboard_centroid = super::super::helpers::entities_centroid_by_bbox(
&self.tabs[i].scene.wire_models_for(&handles), &self.tabs[i].scene.document,
&handles,
); );
let count = entities.len(); let count = entities.len();
self.clipboard = entities; self.clipboard = entities;

View file

@ -1,4 +1,3 @@
use crate::scene::WireModel;
use crate::ui::overlay::GridPlane; use crate::ui::overlay::GridPlane;
use acadrust::tables::Ucs; use acadrust::tables::Ucs;
@ -253,28 +252,27 @@ pub(super) fn polar_constrain_near(
// ── Clipboard / selection helpers ────────────────────────────────────────── // ── Clipboard / selection helpers ──────────────────────────────────────────
/// Compute the centroid of a set of wire models (average of all points). /// Cheap copy/paste anchor: the mean of each entity's bounding-box centre.
pub(super) fn entities_centroid(wires: &[WireModel]) -> glam::DVec3 { /// Replaces averaging every tessellated wire vertex, which cost O(total
// Reconstruct each vertex's absolute f64 from the double-single high/low /// geometry) and stalled a whole-drawing copy. The anchor only sets which point
// pair and accumulate in f64: summing the f32 `points` alone at UTM scale /// of the selection sits under the cursor, so a per-entity bbox centre is an
// (~5.7e6) both quantizes each term ~0.5 m and loses low bits across the /// equally valid, far cheaper origin.
// running total, drifting the paste base / block base metres off. pub(super) fn entities_centroid_by_bbox(
doc: &acadrust::CadDocument,
handles: &[acadrust::Handle],
) -> glam::DVec3 {
let mut sum = glam::DVec3::ZERO; let mut sum = glam::DVec3::ZERO;
let mut count = 0usize; let mut count = 0usize;
for w in wires { for &h in handles {
for (i, p) in w.points.iter().enumerate() { let Some(e) = doc.get_entity(h) else { continue };
// Wire models carry NaN points as separators between disjoint let bb = e.as_entity().bounding_box();
// segments; summing them poisons the whole centroid into NaN, let c = glam::DVec3::new(
// which then makes every paste base point NaN. (#129) (bb.min.x + bb.max.x) * 0.5,
if !p[0].is_finite() || !p[1].is_finite() || !p[2].is_finite() { (bb.min.y + bb.max.y) * 0.5,
continue; (bb.min.z + bb.max.z) * 0.5,
}
let l = w.points_low.get(i).copied().unwrap_or([0.0; 3]);
sum += glam::DVec3::new(
p[0] as f64 + l[0] as f64,
p[1] as f64 + l[1] as f64,
p[2] as f64 + l[2] as f64,
); );
if c.x.is_finite() && c.y.is_finite() && c.z.is_finite() {
sum += c;
count += 1; count += 1;
} }
} }