Merge PR #322: perf — responsive large copy/paste + point picking
External contributor sLuCHa (sLuCHaa). Gates O(k²) snap intersection passes, O(1) handle lookups in preview, bbox paste anchor/ghost, count-only properties panel for huge selections, incremental paste mesh.
This commit is contained in:
commit
17f46968d8
7 changed files with 143 additions and 35 deletions
|
|
@ -2085,7 +2085,12 @@ impl OpenCADStudio {
|
|||
})
|
||||
.collect();
|
||||
self.merge_clipboard_ext_objects(i, &by_index);
|
||||
self.tabs[i].scene.populate_meshes_from_document();
|
||||
// Incremental: `add_entity` already tessellated every pasted top-level
|
||||
// solid, and existing document solids are still cached — so only newly
|
||||
// introduced block-definition solids need building. The full rebuild
|
||||
// would clear and re-tessellate the entire document (every solid in the
|
||||
// drawing) on each paste, which is what made a large paste stall.
|
||||
self.tabs[i].scene.populate_missing_meshes_from_document();
|
||||
by_index
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,9 @@ impl OpenCADStudio {
|
|||
.iter()
|
||||
.filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned())
|
||||
.collect();
|
||||
self.clipboard_centroid = super::super::helpers::entities_centroid(
|
||||
&self.tabs[i].scene.wire_models_for(&handles),
|
||||
self.clipboard_centroid = super::super::helpers::entities_centroid_by_bbox(
|
||||
&self.tabs[i].scene.document,
|
||||
&handles,
|
||||
);
|
||||
self.clipboard = entities;
|
||||
self.clipboard_deps = super::super::ClipboardDeps::capture(
|
||||
|
|
@ -158,8 +159,9 @@ impl OpenCADStudio {
|
|||
.iter()
|
||||
.filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned())
|
||||
.collect();
|
||||
self.clipboard_centroid = super::super::helpers::entities_centroid(
|
||||
&self.tabs[i].scene.wire_models_for(&handles),
|
||||
self.clipboard_centroid = super::super::helpers::entities_centroid_by_bbox(
|
||||
&self.tabs[i].scene.document,
|
||||
&handles,
|
||||
);
|
||||
let count = entities.len();
|
||||
self.clipboard = entities;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use crate::scene::WireModel;
|
||||
use crate::ui::overlay::GridPlane;
|
||||
use acadrust::tables::Ucs;
|
||||
|
||||
|
|
@ -253,28 +252,27 @@ pub(super) fn polar_constrain_near(
|
|||
|
||||
// ── Clipboard / selection helpers ──────────────────────────────────────────
|
||||
|
||||
/// Compute the centroid of a set of wire models (average of all points).
|
||||
pub(super) fn entities_centroid(wires: &[WireModel]) -> glam::DVec3 {
|
||||
// Reconstruct each vertex's absolute f64 from the double-single high/low
|
||||
// pair and accumulate in f64: summing the f32 `points` alone at UTM scale
|
||||
// (~5.7e6) both quantizes each term ~0.5 m and loses low bits across the
|
||||
// running total, drifting the paste base / block base metres off.
|
||||
/// Cheap copy/paste anchor: the mean of each entity's bounding-box centre.
|
||||
/// Replaces averaging every tessellated wire vertex, which cost O(total
|
||||
/// geometry) and stalled a whole-drawing copy. The anchor only sets which point
|
||||
/// of the selection sits under the cursor, so a per-entity bbox centre is an
|
||||
/// equally valid, far cheaper origin.
|
||||
pub(super) fn entities_centroid_by_bbox(
|
||||
doc: &acadrust::CadDocument,
|
||||
handles: &[acadrust::Handle],
|
||||
) -> glam::DVec3 {
|
||||
let mut sum = glam::DVec3::ZERO;
|
||||
let mut count = 0usize;
|
||||
for w in wires {
|
||||
for (i, p) in w.points.iter().enumerate() {
|
||||
// Wire models carry NaN points as separators between disjoint
|
||||
// segments; summing them poisons the whole centroid into NaN,
|
||||
// which then makes every paste base point NaN. (#129)
|
||||
if !p[0].is_finite() || !p[1].is_finite() || !p[2].is_finite() {
|
||||
continue;
|
||||
}
|
||||
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,
|
||||
);
|
||||
for &h in handles {
|
||||
let Some(e) = doc.get_entity(h) else { continue };
|
||||
let bb = e.as_entity().bounding_box();
|
||||
let c = glam::DVec3::new(
|
||||
(bb.min.x + bb.max.x) * 0.5,
|
||||
(bb.min.y + bb.max.y) * 0.5,
|
||||
(bb.min.z + bb.max.z) * 0.5,
|
||||
);
|
||||
if c.x.is_finite() && c.y.is_finite() && c.z.is_finite() {
|
||||
sum += c;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@ use crate::scene::view::dispatch;
|
|||
use crate::ui;
|
||||
use acadrust::{EntityType, Handle};
|
||||
|
||||
/// Above this many selected objects the Properties panel skips per-entity
|
||||
/// property aggregation (which is O(n) per row, plus an O(n²) group filter) and
|
||||
/// shows a count-only summary instead. Bulk edits still go through the ribbon.
|
||||
const MAX_PROP_AGGREGATE: usize = 2_000;
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Rebuild the PropertiesPanel from the current entity selection.
|
||||
/// Preserves UI state (open pickers, edit buffer) across refreshes.
|
||||
|
|
@ -432,6 +437,23 @@ impl OpenCADStudio {
|
|||
..Default::default()
|
||||
}
|
||||
}
|
||||
// Property aggregation is O(n) per row plus an O(n²) group filter
|
||||
// (`group.handles.contains` scans per entity), stalling the rebuild
|
||||
// for seconds at tens of thousands of objects. Above the cap show a
|
||||
// count-only panel; bulk edits still go through the ribbon.
|
||||
n if n > MAX_PROP_AGGREGATE => ui::PropertiesPanel {
|
||||
title: format!("{} objects selected", n),
|
||||
layer_combo: iced::widget::combo_box::State::new(layer_names.clone()),
|
||||
linetype_combo: iced::widget::combo_box::State::new(linetype_items.clone()),
|
||||
lineweight_combo: iced::widget::combo_box::State::new(
|
||||
ui::properties::lw_options(),
|
||||
),
|
||||
hatch_pattern_combo: iced::widget::combo_box::State::new(
|
||||
crate::scene::model::hatch_patterns::names(),
|
||||
),
|
||||
linetype_items,
|
||||
..Default::default()
|
||||
},
|
||||
_ => {
|
||||
let groups = build_selection_groups(&selected);
|
||||
let active_group = selected_group
|
||||
|
|
|
|||
Loading…
Reference in a new issue