diff --git a/src/scene/mod.rs b/src/scene/mod.rs index bde91392..030b1376 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -6060,6 +6060,60 @@ impl Scene { self.add_entity(entity) } + /// Duplicate the anonymous block `src_name`, transforming every sub-entity + /// by `t`, and return the new block's name. A dimension's drawn geometry + /// lives in such a baked `*D` block, so a copied dimension needs its own + /// transformed block — otherwise it still references the source block and + /// renders on top of the original instead of at the copy. Returns None when + /// the source block is missing or empty. (#161) + fn clone_transformed_block(&mut self, src_name: &str, t: &EntityTransform) -> Option { + let sub_handles = self + .document + .block_records + .iter() + .find(|br| br.name.eq_ignore_ascii_case(src_name)) + .map(|br| br.entity_handles.clone())?; + if sub_handles.is_empty() { + return None; + } + // Smallest free `*D` anonymous name. + let mut n = 0u64; + let new_name = loop { + let cand = format!("*D{n}"); + if self.document.block_records.get(&cand).is_none() { + break cand; + } + n += 1; + }; + 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 br = acadrust::tables::BlockRecord::new(&new_name); + br.handle = br_handle; + br.block_entity_handle = block_handle; + br.block_end_handle = end_handle; + self.document.block_records.add(br).ok()?; + let mut block = Block::new(&new_name, acadrust::types::Vector3::ZERO); + block.common.handle = block_handle; + block.common.owner_handle = br_handle; + self.document.add_entity(EntityType::Block(block)).ok()?; + let mut block_end = BlockEnd::new(); + 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); + } + } + Some(new_name) + } + pub fn copy_entities(&mut self, handles: &[Handle], t: &EntityTransform) -> Vec { let clones: Vec = handles .iter() @@ -6068,6 +6122,19 @@ impl Scene { let mut new_handles = Vec::with_capacity(clones.len()); for mut entity in clones { view::dispatch::apply_transform(&mut entity, t); + // A dimension draws from its baked `*D` block; give the copy its own + // transformed block so it lands at the copy position rather than + // rendering on top of the source. (#161) + if let EntityType::Dimension(d) = &entity { + let bn = d.base().block_name.clone(); + if !bn.trim().is_empty() { + if let Some(new_bn) = self.clone_transformed_block(&bn, t) { + if let EntityType::Dimension(d) = &mut entity { + d.base_mut().block_name = new_bn; + } + } + } + } Self::reset_clone_subhandles(&mut self.document, &mut entity); entity.common_mut().handle = Handle::NULL; let h = self.document.add_entity(entity).unwrap_or(Handle::NULL); diff --git a/tests/dim_copy_check.rs b/tests/dim_copy_check.rs new file mode 100644 index 00000000..ac35fe2f --- /dev/null +++ b/tests/dim_copy_check.rs @@ -0,0 +1,72 @@ +// Regression for #161: COPY must duplicate a dimension's baked block so the +// copy renders at the copy position, not on top of the original. +use acadrust::entities::{Dimension, DimensionLinear, Line}; +use acadrust::tables::BlockRecord; +use acadrust::types::Vector3; +use acadrust::{EntityType, Handle}; +use glam::DVec3; +use OpenCADStudio::command::EntityTransform; +use OpenCADStudio::scene::Scene; + +#[test] +fn copy_dimension_duplicates_its_block() { + let mut scene = Scene::new(); + + // A baked *D0 block holding one line at (0,0)-(10,0). + let br_h = Handle::new(scene.document.next_handle()); + let mut br = BlockRecord::new("*D0"); + br.handle = br_h; + scene.document.block_records.add(br).unwrap(); + + let mut sub = Line::new(); + sub.start = Vector3::new(0.0, 0.0, 0.0); + sub.end = Vector3::new(10.0, 0.0, 0.0); + let mut sub_e = EntityType::Line(sub); + sub_e.common_mut().owner_handle = br_h; // route into *D0 + scene.document.add_entity(sub_e).unwrap(); + + // A linear dimension whose drawn geometry is that baked block. + let mut dim = DimensionLinear::new( + Vector3::new(0.0, 0.0, 0.0), + Vector3::new(10.0, 0.0, 0.0), + ); + dim.base.block_name = "*D0".to_string(); + let dim_h = scene.add_entity(EntityType::Dimension(Dimension::Linear(dim))); + + // Copy the dimension by (0, 50). + let copies = scene.copy_entities( + &[dim_h], + &EntityTransform::Translate(DVec3::new(0.0, 50.0, 0.0)), + ); + assert_eq!(copies.len(), 1); + let copy_h = copies[0]; + + // The copy must reference its OWN block, not the source's. + let copy_block = match scene.document.get_entity(copy_h) { + Some(EntityType::Dimension(d)) => d.base().block_name.clone(), + _ => panic!("copy is not a dimension"), + }; + assert_ne!(copy_block, "*D0", "copy must get its own block"); + assert!(!copy_block.trim().is_empty(), "copy block name must be set"); + + // The copy block's sub-line must be translated by (0, 50): (0,50)-(10,50). + let new_br = scene + .document + .block_records + .iter() + .find(|b| b.name == copy_block) + .expect("copy block record exists"); + let subh = *new_br + .entity_handles + .first() + .expect("copy block has a sub-entity"); + match scene.document.get_entity(subh) { + Some(EntityType::Line(l)) => assert!( + (l.start.y - 50.0).abs() < 1e-6 && (l.end.y - 50.0).abs() < 1e-6, + "copy block sub-line not translated: {:?}-{:?}", + (l.start.x, l.start.y), + (l.end.x, l.end.y) + ), + other => panic!("copy block sub is not a line: {:?}", other.map(std::mem::discriminant)), + } +}