External contribution by sLuCHa. A dimension draws from its baked *D block (baked in absolute WCS); the in-drawing COPY re-points block_name to a transformed copy (#161) but clipboard PASTE did not, so a pasted dimension rendered at the source location (same drawing) or was missing (cross drawing). Copy now snapshots each copied dimension's *D block into the clipboard (dim_blocks); paste builds a fresh copy transformed by the paste offset and re-points block_name — same- and cross-drawing alike, no orphan blocks. Refactors out shared define_transformed_block / snapshot_block helpers (behaviour-preserving). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
8498b08abf
3 changed files with 121 additions and 34 deletions
|
|
@ -2012,6 +2012,35 @@ impl OpenCADStudio {
|
|||
if let Some(t) = &translate {
|
||||
crate::scene::view::dispatch::apply_transform(&mut entity, t);
|
||||
}
|
||||
// A dimension draws from its baked `*D` block (baked in WCS), so
|
||||
// give the paste its own transformed copy of that block and
|
||||
// re-point it — otherwise the pasted dimension renders at the
|
||||
// source location instead of the paste point. The block was
|
||||
// snapshotted into the clipboard at copy time, so this works
|
||||
// cross-drawing too. Mirrors the in-drawing copy. (#290, #161)
|
||||
if let acadrust::EntityType::Dimension(d) = &entity {
|
||||
let bn = d.base().block_name.clone();
|
||||
if !bn.trim().is_empty() {
|
||||
let subs = self
|
||||
.clipboard_deps
|
||||
.dim_blocks
|
||||
.iter()
|
||||
.find(|b| b.name.eq_ignore_ascii_case(&bn))
|
||||
.map(|def| def.entities.clone());
|
||||
if let Some(subs) = subs {
|
||||
let bt = translate.clone().unwrap_or(
|
||||
crate::command::EntityTransform::Translate(glam::DVec3::ZERO),
|
||||
);
|
||||
if let Some(new_bn) =
|
||||
self.tabs[i].scene.define_transformed_block(&subs, &bt)
|
||||
{
|
||||
if let acadrust::EntityType::Dimension(d) = &mut entity {
|
||||
d.base_mut().block_name = new_bn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.add_entity_clone(entity)
|
||||
})
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -779,6 +779,12 @@ pub struct ClipboardDeps {
|
|||
/// reference doesn't render empty in a drawing that lacks the
|
||||
/// definition. (#135)
|
||||
pub blocks: Vec<BlockDef>,
|
||||
/// Baked `*D` blocks referenced by copied dimensions, snapshotted from the
|
||||
/// source drawing. On paste each pasted dimension gets its own transformed
|
||||
/// copy of its block (its geometry is baked in WCS, so without this the
|
||||
/// paste renders at the source location — or, cross-drawing, not at all).
|
||||
/// See #290 (mirrors the in-drawing copy's #161 fix).
|
||||
pub dim_blocks: Vec<BlockDef>,
|
||||
/// Extension-dictionary object subtrees hanging off the copied entities
|
||||
/// (XCLIP spatial filters, attached XRecords, …). Each entity's whole
|
||||
/// `xdictionary` graph is snapshotted so a cross-drawing paste recreates it
|
||||
|
|
@ -878,6 +884,27 @@ impl ClipboardDeps {
|
|||
}
|
||||
}
|
||||
|
||||
// Baked `*D` blocks for copied dimensions (top-level and inside captured
|
||||
// blocks), so a pasted dimension can be given its own transformed block
|
||||
// instead of aliasing the source's — whose geometry is baked in WCS at
|
||||
// the source location. (#290)
|
||||
let mut dim_block_names: BTreeSet<String> = BTreeSet::new();
|
||||
for e in entities
|
||||
.iter()
|
||||
.chain(blocks.iter().flat_map(|d| d.entities.iter()))
|
||||
{
|
||||
if let EntityType::Dimension(d) = e {
|
||||
let bn = d.base().block_name.clone();
|
||||
if !bn.trim().is_empty() {
|
||||
dim_block_names.insert(bn);
|
||||
}
|
||||
}
|
||||
}
|
||||
let dim_blocks: Vec<BlockDef> = dim_block_names
|
||||
.iter()
|
||||
.filter_map(|n| Self::snapshot_block(doc, n))
|
||||
.collect();
|
||||
|
||||
ClipboardDeps {
|
||||
layers: layers
|
||||
.iter()
|
||||
|
|
@ -896,6 +923,7 @@ impl ClipboardDeps {
|
|||
.filter_map(|n| doc.dim_styles.get(n).cloned())
|
||||
.collect(),
|
||||
blocks,
|
||||
dim_blocks,
|
||||
ext_objects,
|
||||
}
|
||||
}
|
||||
|
|
@ -958,43 +986,55 @@ impl ClipboardDeps {
|
|||
}
|
||||
let mut defs = Vec::new();
|
||||
while let Some(name) = queue.pop() {
|
||||
let Some(br) = doc.block_records.get(&name) else {
|
||||
let Some(def) = Self::snapshot_block(doc, &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;
|
||||
}
|
||||
// Follow nested INSERTs so their definitions are captured too.
|
||||
for e in &def.entities {
|
||||
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.push(def);
|
||||
}
|
||||
defs
|
||||
}
|
||||
|
||||
/// Snapshot one block definition as a portable `BlockDef`: its base point
|
||||
/// and owned entities, minus the structural Block/BlockEnd markers. Returns
|
||||
/// None for model/paper space and xref blocks (not portable definitions).
|
||||
fn snapshot_block(doc: &acadrust::CadDocument, name: &str) -> Option<BlockDef> {
|
||||
use acadrust::EntityType;
|
||||
let br = doc.block_records.get(name)?;
|
||||
if name.starts_with("*Model_Space")
|
||||
|| name.starts_with("*Paper_Space")
|
||||
|| br.flags.is_xref
|
||||
{
|
||||
return None;
|
||||
}
|
||||
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;
|
||||
}
|
||||
owned.push(e.clone());
|
||||
}
|
||||
Some(BlockDef {
|
||||
name: name.to_string(),
|
||||
base_point,
|
||||
entities: owned,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.layers.is_empty()
|
||||
&& self.linetypes.is_empty()
|
||||
|
|
|
|||
|
|
@ -223,7 +223,26 @@ impl Scene {
|
|||
.iter()
|
||||
.find(|br| br.name.eq_ignore_ascii_case(src_name))
|
||||
.map(|br| br.entity_handles.clone())?;
|
||||
if sub_handles.is_empty() {
|
||||
let subs: Vec<EntityType> = sub_handles
|
||||
.iter()
|
||||
.filter_map(|&sh| self.document.get_entity(sh).cloned())
|
||||
.collect();
|
||||
self.define_transformed_block(&subs, t)
|
||||
}
|
||||
|
||||
/// Build a fresh anonymous `*D<n>` block from `subs` — a source block's
|
||||
/// sub-entities in its own (WCS-baked) coordinates — transforming each by
|
||||
/// `t`, and return the new block's name. Shared by the in-drawing copy
|
||||
/// (source block still lives in this document, via `clone_transformed_block`)
|
||||
/// and clipboard paste (source block snapshotted into the clipboard, so a
|
||||
/// pasted dimension gets its own transformed block cross-drawing too — see
|
||||
/// `finalize_paste`, #290). Returns None when `subs` is empty.
|
||||
pub(crate) fn define_transformed_block(
|
||||
&mut self,
|
||||
subs: &[EntityType],
|
||||
t: &EntityTransform,
|
||||
) -> Option<String> {
|
||||
if subs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Smallest free `*D<n>` anonymous name.
|
||||
|
|
@ -252,14 +271,13 @@ impl Scene {
|
|||
block_end.common.handle = end_handle;
|
||||
block_end.common.owner_handle = br_handle;
|
||||
self.document.add_entity(EntityType::BlockEnd(block_end)).ok()?;
|
||||
for sh in sub_handles {
|
||||
if let Some(mut sub) = self.document.get_entity(sh).cloned() {
|
||||
view::dispatch::apply_transform(&mut sub, t);
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut sub);
|
||||
sub.common_mut().handle = Handle::NULL;
|
||||
sub.common_mut().owner_handle = br_handle;
|
||||
let _ = self.document.add_entity(sub);
|
||||
}
|
||||
for sub in subs {
|
||||
let mut sub = sub.clone();
|
||||
view::dispatch::apply_transform(&mut sub, t);
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut sub);
|
||||
sub.common_mut().handle = Handle::NULL;
|
||||
sub.common_mut().owner_handle = br_handle;
|
||||
let _ = self.document.add_entity(sub);
|
||||
}
|
||||
Some(new_name)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue