forked from 1b-agent/opencadstudio-upstream
entity_wires() returns the memoized Arc instead of a per-call deep copy; MESH entities over 50k vertices ship no per-vertex snap/key tables (matching PolyfaceMesh/PolygonMesh); the Face3D/other render split caches a None marker and uses the base set directly when there is no Face3D wire; test fix for the depth-map BlockCache::build signature. Reviewed: correctness sound — wire AABB comes from entity_aabb (not key_vertices) so large meshes stay selectable; snap consumers are empty-guarded; the Arc/strong_count reasoning for try_resident_patch holds. One consistent pre-existing quirk noted (auto_fit_viewport centroid excludes tableless meshes, as Poly*Mesh already do).
This commit is contained in:
commit
1803f10264
6 files changed
+70
-34
No files matched your search
@@ -1335,10 +1335,11 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
let (wx0, wy0, wx1, wy1) = (x0 as f32, y0 as f32, x1 as f32, y1 as f32);
|
||||
let wires: Vec<_> = scene
|
||||
.entity_wires()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter(|w| {
|
||||
w.aabb[0] <= wx1 && w.aabb[2] >= wx0 && w.aabb[1] <= wy1 && w.aabb[3] >= wy0
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
let hatches = scene.paper_canvas_hatches();
|
||||
let wipeouts = scene.paper_canvas_wipeouts();
|
||||
@@ -1388,7 +1389,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
pub(super) fn layout_plot_params(
|
||||
&self,
|
||||
) -> (
|
||||
Vec<crate::scene::WireModel>,
|
||||
std::sync::Arc<Vec<crate::scene::WireModel>>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
f64,
|
||||
@@ -1496,7 +1497,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
let mut x1 = f32::NEG_INFINITY;
|
||||
let mut y1 = f32::NEG_INFINITY;
|
||||
let mut any = false;
|
||||
for w in scene.entity_wires() {
|
||||
for w in scene.entity_wires().iter() {
|
||||
let picked = crate::scene::Scene::handle_from_wire_name(&w.name)
|
||||
.is_some_and(|h| set.contains(&h));
|
||||
if !picked {
|
||||
@@ -2150,8 +2151,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
let (wx0, wy0, wx1, wy1) = (x0 as f32, y0 as f32, x1 as f32, y1 as f32);
|
||||
let wires: Vec<_> = scene
|
||||
.entity_wires()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter(|w| w.aabb[0] <= wx1 && w.aabb[2] >= wx0 && w.aabb[1] <= wy1 && w.aabb[3] >= wy0)
|
||||
.cloned()
|
||||
.collect();
|
||||
let hatches: Vec<_> = scene.paper_canvas_hatches().as_ref().clone();
|
||||
let wipeouts: Vec<_> = scene.paper_canvas_wipeouts().as_ref().clone();
|
||||
|
||||
+17
-6
@@ -685,12 +685,23 @@ impl TruckConvertible for Mesh {
|
||||
}
|
||||
}
|
||||
|
||||
let snap_pts: Vec<(glam::DVec3, SnapHint)> = self
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|v| (glam::DVec3::new(v.x, v.y, v.z), SnapHint::Node))
|
||||
.collect();
|
||||
let key_vertices: Vec<[f64; 3]> = self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();
|
||||
// Per-vertex snap tables cost ~56 B/vertex and are retained in every
|
||||
// copy of the wire set. On NWD-scale imports (#358: tens of millions
|
||||
// of mesh vertices) that is GBs for snap targets far too dense to
|
||||
// pick apart on screen — so past this size ship none, which is what
|
||||
// PolyfaceMesh / PolygonMesh already do at any size.
|
||||
const SNAP_TABLE_MAX_VERTICES: usize = 50_000;
|
||||
let (snap_pts, key_vertices) = if self.vertices.len() > SNAP_TABLE_MAX_VERTICES {
|
||||
(Vec::new(), Vec::new())
|
||||
} else {
|
||||
(
|
||||
self.vertices
|
||||
.iter()
|
||||
.map(|v| (glam::DVec3::new(v.x, v.y, v.z), SnapHint::Node))
|
||||
.collect(),
|
||||
self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect(),
|
||||
)
|
||||
};
|
||||
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
|
||||
@@ -36,7 +36,7 @@ pub struct PrintOptions {
|
||||
// is native-only; the web build gets a stub so the call site still compiles.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn print_wires(
|
||||
_wires: Vec<WireModel>,
|
||||
_wires: std::sync::Arc<Vec<WireModel>>,
|
||||
_hatches: Vec<HatchModel>,
|
||||
_wipeouts: Vec<HatchModel>,
|
||||
_paper_w: f64,
|
||||
@@ -57,7 +57,7 @@ pub fn list_printers() -> Vec<String> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn print_wires_with(
|
||||
_wires: Vec<WireModel>,
|
||||
_wires: std::sync::Arc<Vec<WireModel>>,
|
||||
_hatches: Vec<HatchModel>,
|
||||
_wipeouts: Vec<HatchModel>,
|
||||
_paper_w: f64,
|
||||
@@ -87,7 +87,7 @@ pub fn print_existing_pdf(_path: &std::path::Path, _opts: &PrintOptions) -> Resu
|
||||
/// Returns `Ok(printer_name)` on success or `Err(message)` on failure.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn print_wires(
|
||||
wires: Vec<WireModel>,
|
||||
wires: std::sync::Arc<Vec<WireModel>>,
|
||||
hatches: Vec<HatchModel>,
|
||||
wipeouts: Vec<HatchModel>,
|
||||
paper_w: f64,
|
||||
@@ -150,7 +150,7 @@ pub fn list_printers() -> Vec<String> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn print_wires_with(
|
||||
wires: Vec<WireModel>,
|
||||
wires: std::sync::Arc<Vec<WireModel>>,
|
||||
hatches: Vec<HatchModel>,
|
||||
wipeouts: Vec<HatchModel>,
|
||||
paper_w: f64,
|
||||
|
||||
+16
-6
@@ -1069,9 +1069,15 @@ pub struct Scene {
|
||||
/// [`WIRE_CONTENT_GEN`] id. `split_face3d_wires` is an O(N) per-wire
|
||||
/// handle lookup + clone that otherwise re-runs every frame. A map, not a
|
||||
/// slot: a paper frame walks several sources (sheet + viewports).
|
||||
///
|
||||
/// `None` for the second element means "no Face3D wire in this set — use
|
||||
/// the base set itself". The base `Arc` is deliberately NOT stored here:
|
||||
/// `try_resident_patch` can only move a resident set out for an
|
||||
/// incremental patch while its `Arc` is uniquely held, so pinning it in
|
||||
/// this cache would silently force a full rebuild on every edit (#358).
|
||||
#[allow(clippy::type_complexity)]
|
||||
split_cache:
|
||||
RefCell<HashMap<u64, (Arc<Vec<WireModel>>, Arc<Vec<WireModel>>)>>,
|
||||
RefCell<HashMap<u64, (Arc<Vec<WireModel>>, Option<Arc<Vec<WireModel>>>)>>,
|
||||
/// Cached `selected ∪ hover` handle set for the GPU xray overlay, keyed by
|
||||
/// `selection_generation`. Rebuilt only when the selection changes so
|
||||
/// `build_primitive` doesn't clone the set every frame.
|
||||
@@ -2870,9 +2876,9 @@ impl Scene {
|
||||
/// Collect closed polygon outlines (world XY) from the current layout.
|
||||
pub fn closed_outlines(&self) -> Vec<Vec<[f32; 2]>> {
|
||||
self.entity_wires()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter_map(|wire| {
|
||||
let pts = wire.points;
|
||||
let pts = &wire.points;
|
||||
if pts.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
@@ -2891,7 +2897,7 @@ impl Scene {
|
||||
// boundary commands) see one vertex per corner — not the doubled
|
||||
// ring that otherwise shows two grips at every corner.
|
||||
let mut ring: Vec<[f32; 2]> = Vec::with_capacity(pts.len());
|
||||
for p in &pts {
|
||||
for p in pts {
|
||||
if !p[0].is_finite() || !p[1].is_finite() {
|
||||
continue;
|
||||
}
|
||||
@@ -3361,8 +3367,12 @@ impl Scene {
|
||||
}
|
||||
|
||||
/// Build WireModels from all document entities + optional preview wire.
|
||||
pub fn entity_wires(&self) -> Vec<WireModel> {
|
||||
(*self.entity_wires_arc()).clone()
|
||||
///
|
||||
/// Returns the memoized set by `Arc` — callers only ever iterate it, and
|
||||
/// the previous per-call deep clone was a full copy of every wire buffer
|
||||
/// (hundreds of MB on large mesh imports, #358).
|
||||
pub fn entity_wires(&self) -> Arc<Vec<WireModel>> {
|
||||
self.entity_wires_arc()
|
||||
}
|
||||
|
||||
/// Per-entity normalized draw-order depth, keyed by entity handle value.
|
||||
|
||||
+26
-13
@@ -1251,9 +1251,18 @@ impl Scene {
|
||||
// each frame — for every source, since all ids are stable now.
|
||||
let (face3d_wires, other_arc) = {
|
||||
let cached = { self.split_cache.borrow().get(&wire_content_id).cloned() };
|
||||
cached.unwrap_or_else(|| {
|
||||
let (f, o) = split_face3d_wires(&base_arc, &self.document);
|
||||
let (fa, oa) = (Arc::new(f), Arc::new(o));
|
||||
let (fa, oa) = cached.unwrap_or_else(|| {
|
||||
// No Face3D wire at all (pure 2-D drawings, mesh imports):
|
||||
// "others" would be a wire-for-wire copy of the base set —
|
||||
// mark it `None` and use the base set directly instead of
|
||||
// duplicating it (#358). The base Arc itself must not be
|
||||
// stored in the cache (see the `split_cache` field docs).
|
||||
let (fa, oa) = if base_arc.iter().any(|w| is_face3d_wire(w, &self.document)) {
|
||||
let (f, o) = split_face3d_wires(&base_arc, &self.document);
|
||||
(Arc::new(f), Some(Arc::new(o)))
|
||||
} else {
|
||||
(Arc::new(Vec::new()), None)
|
||||
};
|
||||
let mut c = self.split_cache.borrow_mut();
|
||||
// Ids change on rebuild, so old keys die naturally; the cap
|
||||
// just bounds pathological churn.
|
||||
@@ -1262,7 +1271,8 @@ impl Scene {
|
||||
}
|
||||
c.insert(wire_content_id, (fa.clone(), oa.clone()));
|
||||
(fa, oa)
|
||||
})
|
||||
});
|
||||
(fa, oa.unwrap_or_else(|| Arc::clone(&base_arc)))
|
||||
};
|
||||
// Base wire set — the cached `other` Arc directly, never cloned to
|
||||
// append overlays. Preview / interim wires ride in their own small
|
||||
@@ -1569,9 +1579,19 @@ pub(crate) fn resolve_pattern(
|
||||
(pat_len, pat)
|
||||
}
|
||||
|
||||
/// Whether a wire belongs to a Face3D entity, by document handle lookup —
|
||||
/// so no changes to WireModel are needed.
|
||||
fn is_face3d_wire(w: &WireModel, document: &acadrust::CadDocument) -> bool {
|
||||
w.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|v| document.get_entity(Handle::new(v)))
|
||||
.map(|e| matches!(e, EntityType::Face3D(_)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Partition a wire list into (face3d_wires, other_wires).
|
||||
///
|
||||
/// Uses a document handle lookup so no changes to WireModel are needed.
|
||||
/// O(N) per geometry epoch — acceptable since it runs once per epoch.
|
||||
fn split_face3d_wires(
|
||||
wires: &[WireModel],
|
||||
@@ -1580,14 +1600,7 @@ fn split_face3d_wires(
|
||||
let mut face3d = Vec::new();
|
||||
let mut others = Vec::new();
|
||||
for w in wires {
|
||||
let is_face3d = w
|
||||
.name
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.and_then(|v| document.get_entity(Handle::new(v)))
|
||||
.map(|e| matches!(e, EntityType::Face3D(_)))
|
||||
.unwrap_or(false);
|
||||
if is_face3d {
|
||||
if is_face3d_wire(w, document) {
|
||||
face3d.push(w.clone());
|
||||
} else {
|
||||
others.push(w.clone());
|
||||
|
||||
@@ -49,7 +49,7 @@ fn expand_block_mtext(
|
||||
|
||||
let ins = Insert::new("LABEL_BLOCK", insert_at);
|
||||
doc.add_entity(EntityType::Insert(ins.clone())).unwrap();
|
||||
let cache = BlockCache::build(&doc, 1.0, [0.0, 0.0, 0.0, 1.0]);
|
||||
let cache = BlockCache::build(&doc, 1.0, [0.0, 0.0, 0.0, 1.0], &Default::default());
|
||||
expand_insert(
|
||||
&cache,
|
||||
&ins,
|
||||
|
||||
Reference in new issue
Block a user