diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index 4d1b68a84..ce231c425 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -600,6 +600,9 @@ impl OpenCADStudio { // Recreate any layer / linetype / style the copied entities // need but this drawing lacks (cross-drawing paste). (#129) self.merge_clipboard_deps(i); + // …and any block definition the pasted INSERTs reference, + // else a block reference renders empty. (#135) + self.merge_clipboard_blocks(i); let count = self.clipboard.len(); let new_handles: Vec = self .clipboard @@ -1768,6 +1771,25 @@ impl OpenCADStudio { } } + /// Recreate any block definition the pasted INSERTs reference but tab + /// `i`'s document lacks (cross-drawing paste), so the block reference + /// renders its geometry instead of nothing. No-op for same-document + /// pastes. (#135) + pub(super) fn merge_clipboard_blocks(&mut self, i: usize) { + if self.clipboard_deps.blocks.is_empty() { + return; + } + let blocks = self.clipboard_deps.blocks.clone(); + for def in blocks { + if self.tabs[i].scene.document.block_records.get(&def.name).is_some() { + continue; + } + self.tabs[i] + .scene + .define_block_raw(&def.name, def.base_point, def.entities); + } + } + fn restore_pre_cmd_tangent(&mut self) { if let Some(was_on) = self.pre_cmd_tangent.take() { if !was_on { diff --git a/src/app/mod.rs b/src/app/mod.rs index 462e3d8e6..c1cf5a8d7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -651,6 +651,21 @@ pub struct ClipboardDeps { pub linetypes: Vec, pub text_styles: Vec, pub dim_styles: Vec, + /// Block definitions the copied INSERTs reference (transitively), + /// snapshotted from the source drawing. Recreated on paste so a block + /// reference doesn't render empty in a drawing that lacks the + /// definition. (#135) + pub blocks: Vec, +} + +/// A captured block definition: its base point and the entities it owns +/// (in block-local coordinates), minus the structural Block/BlockEnd +/// markers which are rebuilt on paste. +#[derive(Clone)] +pub struct BlockDef { + pub name: String, + pub base_point: acadrust::types::Vector3, + pub entities: Vec, } impl ClipboardDeps { @@ -704,14 +719,70 @@ impl ClipboardDeps { linetypes: ltypes.iter().filter_map(|n| doc.line_types.get(n).cloned()).collect(), text_styles: tstyles.iter().filter_map(|n| doc.text_styles.get(n).cloned()).collect(), dim_styles: dstyles.iter().filter_map(|n| doc.dim_styles.get(n).cloned()).collect(), + blocks: Self::capture_blocks(doc, entities), } } + /// Snapshot every block definition the `entities` reference through an + /// INSERT, walking nested INSERTs transitively. Model/paper space and + /// xref blocks are skipped — those aren't portable definitions. + fn capture_blocks(doc: &acadrust::CadDocument, entities: &[acadrust::EntityType]) -> Vec { + use acadrust::EntityType; + use rustc_hash::FxHashSet; + let mut seen: FxHashSet = FxHashSet::default(); + let mut queue: Vec = Vec::new(); + for e in entities { + if let EntityType::Insert(ins) = e { + if seen.insert(ins.block_name.clone()) { + queue.push(ins.block_name.clone()); + } + } + } + let mut defs = Vec::new(); + while let Some(name) = queue.pop() { + let Some(br) = doc.block_records.get(&name) else { + continue; + }; + if name.starts_with("*Model_Space") + || name.starts_with("*Paper_Space") + || br.flags.is_xref + { + continue; + } + let base_point = match doc.get_entity(br.block_entity_handle) { + Some(EntityType::Block(b)) => b.base_point, + _ => acadrust::types::Vector3::ZERO, + }; + let mut owned = Vec::new(); + for &eh in &br.entity_handles { + let Some(e) = doc.get_entity(eh) else { + continue; + }; + if matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)) { + continue; + } + if let EntityType::Insert(ins) = e { + if seen.insert(ins.block_name.clone()) { + queue.push(ins.block_name.clone()); + } + } + owned.push(e.clone()); + } + defs.push(BlockDef { + name, + base_point, + entities: owned, + }); + } + defs + } + pub fn is_empty(&self) -> bool { self.layers.is_empty() && self.linetypes.is_empty() && self.text_styles.is_empty() && self.dim_styles.is_empty() + && self.blocks.is_empty() } } diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 80920dad6..eac8ef1ad 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -3820,6 +3820,51 @@ impl Scene { Ok(()) } + /// Recreate a block definition verbatim — the entities are already in + /// block-local coordinates (unlike `define_block_from_owned_entities`, + /// which folds in a base offset). No-op if the block already exists. + /// Used when pasting an INSERT whose block this drawing lacks. (#135) + pub fn define_block_raw( + &mut self, + name: &str, + base_point: acadrust::types::Vector3, + entities: Vec, + ) { + if name.is_empty() || self.document.block_records.get(name).is_some() { + return; + } + let next = self.document.next_handle(); + let br_handle = Handle::new(next); + let block_handle = Handle::new(next + 1); + let end_handle = Handle::new(next + 2); + + let mut block_record = acadrust::tables::BlockRecord::new(name); + block_record.handle = br_handle; + block_record.block_entity_handle = block_handle; + block_record.block_end_handle = end_handle; + if self.document.block_records.add(block_record).is_err() { + return; + } + + let mut block = Block::new(name, base_point); + block.common.handle = block_handle; + block.common.owner_handle = br_handle; + let _ = self.document.add_entity(EntityType::Block(block)); + + let mut block_end = BlockEnd::new(); + block_end.common.handle = end_handle; + block_end.common.owner_handle = br_handle; + let _ = self.document.add_entity(EntityType::BlockEnd(block_end)); + + for mut entity in entities { + Self::reset_clone_subhandles(&mut self.document, &mut entity); + entity.common_mut().handle = Handle::NULL; + entity.common_mut().owner_handle = br_handle; + let _ = self.document.add_entity(entity); + } + self.bump_geometry(); + } + fn synced_hatch_models(&self) -> Vec { let layout_block = self.current_layout_block_handle(); let hatch_offset = if self.current_layout == "Model" {