forked from 1b-agent/opencadstudio-upstream
feat(clipboard): paste menu with paste-to-original and paste-as-block
Turn the ribbon Clipboard "Paste" button into a dropdown: - Paste — existing pick-a-point paste. - Paste to Original Coordinates — drops the clipboard at its source coordinates with no prompt. - Paste as Block — wraps the clipboard contents in a new block definition and starts an interactive insert (prompts for the drop point, with the geometry rubber-banding under the cursor). Also fixes two bugs the new commands exposed: - entities_centroid summed the NaN separator points wire models carry, so the paste base point came out NaN; non-finite points are now skipped. A NaN base previously froze block tessellation outright. - PASTEORIG added clipboard entities with their original handles (duplicates on same-document paste); it now goes through add_entity_clone and merges referenced records like the other pastes. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
446430d2b3
commit
47f74a9b98
7 files changed
+183
-10
No files matched your search
@@ -3588,6 +3588,77 @@ impl Scene {
|
||||
Ok(self.add_entity(EntityType::Insert(insert)))
|
||||
}
|
||||
|
||||
/// Define a new block named `name` from `entities` (owned, not yet in the
|
||||
/// document), with `base` as its insertion origin. Unlike
|
||||
/// [`create_block_from_entities`] this does NOT place an insert — the
|
||||
/// caller starts an interactive insert so paste-as-block can prompt for the
|
||||
/// drop point. The geometry comes from the clipboard rather than live
|
||||
/// entities, so there is nothing to stage or erase. (#129)
|
||||
pub fn define_block_from_owned_entities(
|
||||
&mut self,
|
||||
entities: Vec<EntityType>,
|
||||
name: &str,
|
||||
base: glam::Vec3,
|
||||
) -> Result<(), String> {
|
||||
let name = name.trim();
|
||||
if name.is_empty() {
|
||||
return Err("Block name cannot be empty.".into());
|
||||
}
|
||||
if name.starts_with('*') {
|
||||
return Err("Block name cannot start with '*'.".into());
|
||||
}
|
||||
if self.document.block_records.get(name).is_some() {
|
||||
return Err(format!("Block \"{name}\" already exists."));
|
||||
}
|
||||
if entities.is_empty() {
|
||||
return Err("Nothing to make into a block.".into());
|
||||
}
|
||||
|
||||
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;
|
||||
self.document
|
||||
.block_records
|
||||
.add(block_record)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut block = Block::new(name, acadrust::types::Vector3::ZERO);
|
||||
block.common.handle = block_handle;
|
||||
block.common.owner_handle = br_handle;
|
||||
self.document
|
||||
.add_entity(EntityType::Block(block))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
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))
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let local = EntityTransform::Translate(-base);
|
||||
for mut entity in entities {
|
||||
view::dispatch::apply_transform(&mut entity, &local);
|
||||
entity = crate::modules::draw::modify::explode::normalize_entity_for_block(entity);
|
||||
Self::reset_clone_subhandles(&mut self.document, &mut entity);
|
||||
entity.common_mut().handle = Handle::NULL;
|
||||
entity.common_mut().owner_handle = br_handle;
|
||||
self.document
|
||||
.add_entity(entity)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
// Block defns don't render on their own, but the geometry cache must
|
||||
// pick up the new definition so the interactive insert can preview it.
|
||||
self.bump_geometry();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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