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 {
|
if let Some(t) = &translate {
|
||||||
crate::scene::view::dispatch::apply_transform(&mut entity, t);
|
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)
|
self.tabs[i].scene.add_entity_clone(entity)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
||||||
|
|
@ -779,6 +779,12 @@ pub struct ClipboardDeps {
|
||||||
/// reference doesn't render empty in a drawing that lacks the
|
/// reference doesn't render empty in a drawing that lacks the
|
||||||
/// definition. (#135)
|
/// definition. (#135)
|
||||||
pub blocks: Vec<BlockDef>,
|
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
|
/// Extension-dictionary object subtrees hanging off the copied entities
|
||||||
/// (XCLIP spatial filters, attached XRecords, …). Each entity's whole
|
/// (XCLIP spatial filters, attached XRecords, …). Each entity's whole
|
||||||
/// `xdictionary` graph is snapshotted so a cross-drawing paste recreates it
|
/// `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 {
|
ClipboardDeps {
|
||||||
layers: layers
|
layers: layers
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -896,6 +923,7 @@ impl ClipboardDeps {
|
||||||
.filter_map(|n| doc.dim_styles.get(n).cloned())
|
.filter_map(|n| doc.dim_styles.get(n).cloned())
|
||||||
.collect(),
|
.collect(),
|
||||||
blocks,
|
blocks,
|
||||||
|
dim_blocks,
|
||||||
ext_objects,
|
ext_objects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -958,43 +986,55 @@ impl ClipboardDeps {
|
||||||
}
|
}
|
||||||
let mut defs = Vec::new();
|
let mut defs = Vec::new();
|
||||||
while let Some(name) = queue.pop() {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
if name.starts_with("*Model_Space")
|
// Follow nested INSERTs so their definitions are captured too.
|
||||||
|| name.starts_with("*Paper_Space")
|
for e in &def.entities {
|
||||||
|| 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 let EntityType::Insert(ins) = e {
|
||||||
if seen.insert(ins.block_name.clone()) {
|
if seen.insert(ins.block_name.clone()) {
|
||||||
queue.push(ins.block_name.clone());
|
queue.push(ins.block_name.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
owned.push(e.clone());
|
|
||||||
}
|
}
|
||||||
defs.push(BlockDef {
|
defs.push(def);
|
||||||
name,
|
|
||||||
base_point,
|
|
||||||
entities: owned,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
defs
|
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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.layers.is_empty()
|
self.layers.is_empty()
|
||||||
&& self.linetypes.is_empty()
|
&& self.linetypes.is_empty()
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,26 @@ impl Scene {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|br| br.name.eq_ignore_ascii_case(src_name))
|
.find(|br| br.name.eq_ignore_ascii_case(src_name))
|
||||||
.map(|br| br.entity_handles.clone())?;
|
.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;
|
return None;
|
||||||
}
|
}
|
||||||
// Smallest free `*D<n>` anonymous name.
|
// Smallest free `*D<n>` anonymous name.
|
||||||
|
|
@ -252,14 +271,13 @@ impl Scene {
|
||||||
block_end.common.handle = end_handle;
|
block_end.common.handle = end_handle;
|
||||||
block_end.common.owner_handle = br_handle;
|
block_end.common.owner_handle = br_handle;
|
||||||
self.document.add_entity(EntityType::BlockEnd(block_end)).ok()?;
|
self.document.add_entity(EntityType::BlockEnd(block_end)).ok()?;
|
||||||
for sh in sub_handles {
|
for sub in subs {
|
||||||
if let Some(mut sub) = self.document.get_entity(sh).cloned() {
|
let mut sub = sub.clone();
|
||||||
view::dispatch::apply_transform(&mut sub, t);
|
view::dispatch::apply_transform(&mut sub, t);
|
||||||
Self::reset_clone_subhandles(&mut self.document, &mut sub);
|
Self::reset_clone_subhandles(&mut self.document, &mut sub);
|
||||||
sub.common_mut().handle = Handle::NULL;
|
sub.common_mut().handle = Handle::NULL;
|
||||||
sub.common_mut().owner_handle = br_handle;
|
sub.common_mut().owner_handle = br_handle;
|
||||||
let _ = self.document.add_entity(sub);
|
let _ = self.document.add_entity(sub);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Some(new_name)
|
Some(new_name)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue