forked from 1b-agent/opencadstudio-upstream
fix(clipboard): carry block definitions across drawings on paste
Copying an INSERT and pasting it into another drawing rendered nothing: the clipboard snapshotted referenced layers/linetypes/styles but not the block definition itself, so the pasted reference pointed at a block the target drawing didn't have. Capture every block an copied INSERT references — walking nested INSERTs transitively, skipping model/paper space and xref blocks — and store the definition's base point plus its owned entities. On paste, recreate any of those blocks the target drawing lacks via a new define_block_raw (entities kept in block-local coordinates), before adding the pasted references. Same-document pastes are unaffected since the blocks already exist. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
490c0f6cf9
commit
e562738d9e
3 files changed
+138
No files matched your search
@@ -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<Handle> = 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 {
|
||||
|
||||
@@ -651,6 +651,21 @@ pub struct ClipboardDeps {
|
||||
pub linetypes: Vec<acadrust::tables::LineType>,
|
||||
pub text_styles: Vec<acadrust::tables::TextStyle>,
|
||||
pub dim_styles: Vec<acadrust::tables::DimStyle>,
|
||||
/// 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<BlockDef>,
|
||||
}
|
||||
|
||||
/// 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<acadrust::EntityType>,
|
||||
}
|
||||
|
||||
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<BlockDef> {
|
||||
use acadrust::EntityType;
|
||||
use rustc_hash::FxHashSet;
|
||||
let mut seen: FxHashSet<String> = FxHashSet::default();
|
||||
let mut queue: Vec<String> = 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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EntityType>,
|
||||
) {
|
||||
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<HatchModel> {
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let hatch_offset = if self.current_layout == "Model" {
|
||||
|
||||
Reference in new issue
Block a user