From cba2680be016ccf1102b126b8450f6f30ef9051b Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 9 Sep 2026 13:10:21 +0300 Subject: [PATCH] fix: preserve web builds and leader selection order --- src/app/commands/blocks.rs | 24 --------------- src/app/properties.rs | 8 ++--- src/par.rs | 8 +++++ src/scene/pick/interaction_index.rs | 46 ++++++++++++++--------------- src/scene/selection.rs | 20 ++----------- 5 files changed, 38 insertions(+), 68 deletions(-) diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index 0495fa158..d020e42cc 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -106,14 +106,6 @@ impl OpenCADStudio { &handles, ); let count = self.copy_entities_to_clipboard(i, &handles, base); - // Copy to system clipboard as JSON for web/cross-platform support - if let Ok(clipboard_data) = serde_json::to_string(&self.clipboard) { - return Some(iced::clipboard::write(format!( - "OCS_CLIPBOARD_JSON\n{}\n{}", - serde_json::json!([base.x, base.y, base.z]).to_string(), - clipboard_data - )).discard()); - } self.command_line.push_info(crate::tf!( "{} object(s) copied to clipboard.", count @@ -159,14 +151,6 @@ impl OpenCADStudio { if coords.len() == 3 && !handles.is_empty() { let base = glam::DVec3::new(coords[0], coords[1], coords[2]); let count = self.copy_entities_to_clipboard(i, &handles, base); - // Copy to system clipboard as JSON for web/cross-platform support - if let Ok(clipboard_data) = serde_json::to_string(&self.clipboard) { - let _ = iced::clipboard::write(format!( - "OCS_CLIPBOARD_JSON\n{}\n{}", - serde_json::json!([base.x, base.y, base.z]).to_string(), - clipboard_data - )); - } self.command_line.push_info(crate::tf!( "{} object(s) copied to clipboard (base {:.3},{:.3}).", count, @@ -195,14 +179,6 @@ impl OpenCADStudio { &handles, ); let count = self.copy_entities_to_clipboard(i, &handles, base); - // Copy to system clipboard as JSON for web/cross-platform support - if let Ok(clipboard_data) = serde_json::to_string(&self.clipboard) { - let _ = iced::clipboard::write(format!( - "OCS_CLIPBOARD_JSON\n{}\n{}", - serde_json::json!([base.x, base.y, base.z]).to_string(), - clipboard_data - )); - } self.push_undo_snapshot(i, "CUTCLIP"); self.tabs[i].scene.erase_entities(&handles); self.tabs[i].scene.deselect_all(); diff --git a/src/app/properties.rs b/src/app/properties.rs index c2df6d4b3..a3877a41c 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -35,7 +35,7 @@ impl OpenCADStudio { // A 186 468-entity selection spends ~112 ms in here. Split it into the // three phases that can own that: resolving the selected handles, // building the panel, and syncing the ribbon. - let t_all = crate::perf::enabled().then(std::time::Instant::now); + let t_all = crate::perf::enabled().then(iced::time::Instant::now); let i = self.active_tab; if !crate::entities::object_data::cache_is_prepared( &self.tabs[i].scene.object_data_cache, @@ -112,7 +112,7 @@ impl OpenCADStudio { // Everything before this is the prelude: the object-data cache, the // unit context, the layer and linetype lists. let m_prelude = t_all.map(|t| t.elapsed().as_secs_f64() * 1000.0); - let t_handles = crate::perf::enabled().then(std::time::Instant::now); + let t_handles = crate::perf::enabled().then(iced::time::Instant::now); // `selected_entities` resolves every handle to an entity and builds a // vector of pairs; only the handles are wanted here, but the resolution // is what decides membership — a handle whose entity is gone is not in @@ -2196,7 +2196,7 @@ impl OpenCADStudio { // the active group, moving into the working plane, and // aggregating into "varies" rows. `properties-detail` // reports this whole arm as `rest`; this splits it. - let t_arm = crate::perf::enabled().then(std::time::Instant::now); + let t_arm = crate::perf::enabled().then(iced::time::Instant::now); let groups = build_selection_groups(&selected); let t_groups = t_arm.map(|t| t.elapsed().as_secs_f64() * 1000.0); let active_group = selected_group @@ -2372,7 +2372,7 @@ filter={:.1} local={:.1} aggregate={:.1} entities={}", self.tabs[i].properties = new_panel; self.refresh_selected_grips(); let m_panel = t_all.map(|t| t.elapsed().as_secs_f64() * 1000.0); - let t_ribbon = crate::perf::enabled().then(std::time::Instant::now); + let t_ribbon = crate::perf::enabled().then(iced::time::Instant::now); self.sync_ribbon_from_selection(); if let Some(t) = t_all { let ribbon_ms = t_ribbon.map_or(0.0, |r| r.elapsed().as_secs_f64() * 1000.0); diff --git a/src/par.rs b/src/par.rs index 737bf2b90..4be897e7a 100644 --- a/src/par.rs +++ b/src/par.rs @@ -75,3 +75,11 @@ pub mod prelude { impl IterParExt for I {} } + +#[cfg(not(target_arch = "wasm32"))] +pub use rayon::join; + +#[cfg(target_arch = "wasm32")] +pub fn join(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) { + (a(), b()) +} diff --git a/src/scene/pick/interaction_index.rs b/src/scene/pick/interaction_index.rs index e21068f25..e470fddd2 100644 --- a/src/scene/pick/interaction_index.rs +++ b/src/scene/pick/interaction_index.rs @@ -914,17 +914,17 @@ impl InteractionIndex { let ( ((segment_entries, snap_point_entries), (key_vertex_entries, key_segment_entries)), ((fill_triangle_entries, pick_triangle_entries), glyph_entries), - ) = rayon::join( + ) = crate::par::join( || { - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || flatten_entry_parts(segment_parts), || flatten_entry_parts(snap_point_parts), ) }, || { - rayon::join( + crate::par::join( || flatten_entry_parts(key_vertex_parts), || flatten_entry_parts(key_segment_parts), ) @@ -932,7 +932,7 @@ impl InteractionIndex { ) }, || { - let (fill, pick) = rayon::join( + let (fill, pick) = crate::par::join( || flatten_entry_parts(fill_triangle_parts), || flatten_entry_parts(pick_triangle_parts), ); @@ -966,17 +966,17 @@ impl InteractionIndex { let ( ((wires, segments), (snap_points, key_vertices)), ((key_segments, fill_triangles), (pick_triangles, glyphs)), - ) = rayon::join( + ) = crate::par::join( || { - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || SpatialSet::build(wire_entries), || SpatialSet::build(segment_entries), ) }, || { - rayon::join( + crate::par::join( || SpatialSet::build(snap_point_entries), || SpatialSet::build(key_vertex_entries), ) @@ -984,15 +984,15 @@ impl InteractionIndex { ) }, || { - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || SpatialSet::build(key_segment_entries), || SpatialSet::build(fill_triangle_entries), ) }, || { - rayon::join( + crate::par::join( || SpatialSet::build(pick_triangle_entries), || SpatialSet::build(glyph_entries), ) @@ -1054,12 +1054,12 @@ impl InteractionIndex { /// thread. Perspective/orbit hover must never pay this one-time cost. pub fn prepare_screen(&self) { #[cfg(not(target_arch = "wasm32"))] - rayon::join( + crate::par::join( || { - rayon::join( - || rayon::join(|| self.wires.prepare_screen(), || self.segments.prepare_screen()), + crate::par::join( + || crate::par::join(|| self.wires.prepare_screen(), || self.segments.prepare_screen()), || { - rayon::join( + crate::par::join( || self.snap_points.prepare_screen(), || self.key_vertices.prepare_screen(), ) @@ -1067,15 +1067,15 @@ impl InteractionIndex { ) }, || { - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || self.key_segments.prepare_screen(), || self.fill_triangles.prepare_screen(), ) }, || { - rayon::join( + crate::par::join( || self.pick_triangles.prepare_screen(), || self.glyphs.prepare_screen(), ) @@ -1426,9 +1426,9 @@ impl InteractionIndex { // sum. Each closure still calls the same query, so the results are // identical to the sequential ones, order included. let ((wire_indices, segments), ((fill_triangles, pick_triangles), glyphs)) = - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || { let mut indices = self.wires.query_xy_unordered(aabb); indices.extend_from_slice(&self.unbounded_wires); @@ -1440,9 +1440,9 @@ impl InteractionIndex { ) }, || { - rayon::join( + crate::par::join( || { - rayon::join( + crate::par::join( || self.fill_triangles.query_xy_unordered(aabb), || self.pick_triangles.query_xy_unordered(aabb), ) diff --git a/src/scene/selection.rs b/src/scene/selection.rs index 9457136da..792eb2b6c 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -47,6 +47,7 @@ impl Scene { let leaders = self.leaders_by_annotation(); let mut expanded = Vec::with_capacity(handles.len()); for &handle in handles { + let start = expanded.len(); expanded.push(handle); if let Some(EntityType::Leader(leader)) = self.document.get_entity(handle) { if !leader.annotation_handle.is_null() { @@ -56,6 +57,7 @@ impl Scene { if let Some(pointing) = leaders.1.get(&handle) { expanded.extend(pointing.iter().copied()); } + expanded[start..].sort_unstable_by_key(Handle::value); } expanded } @@ -106,23 +108,7 @@ impl Scene { &self, handles: &[Handle], ) -> Vec { - let mut expanded = handles.to_vec(); - let leaders = self.leaders_by_annotation(); - - for &handle in handles { - // LEADER -> annotation. - if let Some(EntityType::Leader(leader)) = self.document.get_entity(handle) { - if !leader.annotation_handle.is_null() { - expanded.push(leader.annotation_handle); - } - } - - // Annotation -> LEADER. - if let Some(pointing) = leaders.1.get(&handle) { - expanded.extend(pointing.iter().copied()); - } - } - + let mut expanded = self.expanded_with_leaders(handles); expanded.sort_unstable_by_key(|handle| handle.value()); expanded.dedup(); expanded