From 217f20d8bc325fa3fbc5011e973480e17d141609 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 29 Aug 2026 22:40:59 +0300 Subject: [PATCH] fix(clipboard): preserve pasted content Reset cross-document ownership, include non-wire and locked objects, and rebuild image caches for embedded OLE content. Refs #934 --- src/app/commands/blocks.rs | 3 --- src/app/commands/inquiry.rs | 12 +----------- src/scene/entity.rs | 14 ++++++-------- src/scene/modify.rs | 35 +++++++++++++++++++++++++++++++---- src/scene/selection.rs | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 26 deletions(-) diff --git a/src/app/commands/blocks.rs b/src/app/commands/blocks.rs index 35e3cc205..2cc1bd4d9 100644 --- a/src/app/commands/blocks.rs +++ b/src/app/commands/blocks.rs @@ -72,7 +72,6 @@ impl OpenCADStudio { .selected_entities() .into_iter() .map(|(h, _)| h) - .filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle)) .collect(); if handles.is_empty() { use crate::modules::draw::select::SelectObjectsCommand; @@ -108,7 +107,6 @@ impl OpenCADStudio { .selected_entities() .into_iter() .map(|(h, _)| h) - .filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle)) .collect(); if handles.is_empty() { use crate::modules::draw::select::SelectObjectsCommand; @@ -135,7 +133,6 @@ impl OpenCADStudio { .selected_entities() .into_iter() .map(|(h, _)| h) - .filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle)) .collect(); if coords.len() == 3 && !handles.is_empty() { let base = glam::DVec3::new(coords[0], coords[1], coords[2]); diff --git a/src/app/commands/inquiry.rs b/src/app/commands/inquiry.rs index bc05b5a7d..d1577fc3e 100644 --- a/src/app/commands/inquiry.rs +++ b/src/app/commands/inquiry.rs @@ -10,17 +10,7 @@ impl OpenCADStudio { // ── Selection utilities ─────────────────────────────────────── "SELECTALL" => { - use crate::scene::Scene; - let handles: Vec = self.tabs[i] - .scene - .entity_wires() - .iter() - .filter_map(|w| Scene::handle_from_wire_name(&w.name)) - .collect(); - let count = handles.len(); - for h in handles { - self.tabs[i].scene.select_entity(h, false); - } + let count = self.tabs[i].scene.select_all_visible(); self.command_line .push_output(crate::tf!("SELECTALL: {} object(s) selected.", count).as_ref()); self.refresh_properties(); diff --git a/src/scene/entity.rs b/src/scene/entity.rs index a257971e1..4234bf8b6 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -2123,16 +2123,14 @@ impl Scene { .collect(); } - /// Tessellate all `Solid3D` entities in the current document into - /// GPU-ready `MeshModel`s and store them in `self.meshes`. - /// - /// Called after loading a document or after undo/redo so that every - /// `Solid3D` entity is represented in the mesh cache. - /// ImageModel for an edited image-bearing entity (RasterImage, or a PDF - /// UNDERLAY resolved through its definition object). `None` for others. - fn image_seed_for(&self, entity: &acadrust::entities::EntityType) -> Option { + /// ImageModel for an image-bearing entity. `None` when it cannot decode. + pub(super) fn image_seed_for( + &self, + entity: &acadrust::entities::EntityType, + ) -> Option { match entity { EntityType::RasterImage(img) => ImageModel::from_raster_image(img), + EntityType::Ole2Frame(ole) => ImageModel::from_ole2frame(ole), EntityType::Underlay(u) => match self.document.objects.get(&u.definition_handle) { Some(acadrust::objects::ObjectType::UnderlayDefinition(def)) => { ImageModel::from_underlay(u, def) diff --git a/src/scene/modify.rs b/src/scene/modify.rs index b75e0eb9d..f376f9307 100644 --- a/src/scene/modify.rs +++ b/src/scene/modify.rs @@ -245,6 +245,24 @@ impl Scene { self.hatches.insert(h, model); } } + let image_bearing = self.document.get_entity(h).is_some_and(|entity| { + matches!( + entity, + EntityType::RasterImage(_) + | EntityType::Ole2Frame(_) + | EntityType::Underlay(_) + ) + }); + if image_bearing { + let model = self + .document + .get_entity(h) + .and_then(|entity| self.image_seed_for(entity)); + self.images.remove(&h); + if let Some(model) = model { + self.images.insert(h, model); + } + } } if preserve_text_orientation { for (h, o) in text_orient_backup { @@ -458,12 +476,21 @@ impl Scene { } } - /// Add a freshly-cloned entity, allocating a new handle for it *and* every - /// inline sub-entity so the copy never shares a handle with its source. - /// Use this (not `add_entity`) whenever inserting a duplicate. (#129) + /// Add a clone to the active space with fresh top-level and inline handles. + /// Source owner handles are document-local and must not cross drawings. + /// (#129, #934) pub fn add_entity_clone(&mut self, mut entity: EntityType) -> Handle { Self::reset_clone_subhandles(&mut self.document, &mut entity); - entity.common_mut().handle = Handle::NULL; + let common = entity.common_mut(); + common.handle = Handle::NULL; + common.owner_handle = Handle::NULL; + common.entity_mode = Some( + if self.current_layout != "Model" && self.active_viewport.is_none() { + 1 + } else { + 2 + }, + ); self.add_entity(entity) } diff --git a/src/scene/selection.rs b/src/scene/selection.rs index db367e61c..925b7d93d 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -69,6 +69,39 @@ impl Scene { self.bump_selection_set(); } + pub fn select_all_visible(&mut self) -> usize { + let block = self.interaction_block_handle(); + let frozen: Option> = self + .interaction_viewport_frozen_layers() + .map(|layers| layers.iter().copied().collect()); + let annotation_scale = self.displayed_annotation_scale_handle(); + let all_visible = self.annotation_all_visible(); + let handles = self + .document + .block_records + .iter() + .find(|record| record.handle == block) + .map(|record| record.entity_handles.clone()) + .unwrap_or_default(); + let selected = handles + .into_iter() + .filter(|handle| self.passes_selection_filter(*handle)) + .filter(|handle| { + self.document.get_entity(*handle).is_some_and(|entity| { + self.resident_entity_visible( + entity, + block, + frozen.as_ref(), + annotation_scale, + all_visible, + ) + }) + }) + .collect(); + self.replace_selection(selected); + self.selected.len() + } + pub(crate) fn selection_fingerprint(&mut self) -> u64 { if self.selection_fingerprint_dirty { let mut fingerprint = self.selected.len() as u64;