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
@@ -1682,7 +1682,7 @@ impl OpenCADStudio {
|
||||
/// this drawing doesn't already have. Each recreated record gets a fresh
|
||||
/// handle from the target document so it can't collide with an existing
|
||||
/// one. No-op for same-document pastes (the records already exist). (#129)
|
||||
fn merge_clipboard_deps(&mut self, i: usize) {
|
||||
pub(super) fn merge_clipboard_deps(&mut self, i: usize) {
|
||||
use acadrust::TableEntry;
|
||||
if self.clipboard_deps.is_empty() {
|
||||
return;
|
||||
|
||||
+56
-3
@@ -5,6 +5,20 @@ use iced::Task;
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// First `"{prefix}{n}"` (n ≥ 1) not already used by a block record in the
|
||||
/// active drawing. Used to auto-name a paste-as-block definition.
|
||||
fn unique_block_name(&self, prefix: &str) -> String {
|
||||
let i = self.active_tab;
|
||||
let mut n = 1;
|
||||
loop {
|
||||
let name = format!("{prefix}{n}");
|
||||
if self.tabs[i].scene.document.block_records.get(&name).is_none() {
|
||||
return name;
|
||||
}
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn dispatch_command(&mut self, cmd: &str) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
// Starting a command closes any open ribbon dropdown (e.g. a style
|
||||
@@ -586,7 +600,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// PASTEORIG — paste at original coordinates (no move to pick point)
|
||||
// PASTEORIG — paste at the entities' original coordinates (no pick).
|
||||
"PASTEORIG" => {
|
||||
if self.clipboard.is_empty() {
|
||||
self.command_line
|
||||
@@ -594,10 +608,15 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
let count = self.clipboard.len();
|
||||
self.push_undo_snapshot(i, "PASTEORIG");
|
||||
for entity in &self.clipboard {
|
||||
self.tabs[i].scene.add_entity(entity.clone());
|
||||
// Recreate any layer / style this drawing lacks, then add
|
||||
// each entity with fresh handles (top-level + inline subs)
|
||||
// so a same-document paste can't duplicate handles. (#129)
|
||||
self.merge_clipboard_deps(i);
|
||||
for entity in self.clipboard.clone() {
|
||||
self.tabs[i].scene.add_entity_clone(entity);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_properties();
|
||||
self.command_line.push_output(&format!(
|
||||
"PASTEORIG: {} object(s) pasted at original coordinates.",
|
||||
count
|
||||
@@ -605,6 +624,40 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
// PASTEBLOCK — wrap the clipboard contents in a new block definition
|
||||
// and place one insert of it at the clipboard's original location.
|
||||
"PASTEBLOCK" => {
|
||||
if self.clipboard.is_empty() {
|
||||
self.command_line
|
||||
.push_error("PASTEBLOCK: clipboard is empty.");
|
||||
} else {
|
||||
self.push_undo_snapshot(i, "PASTEBLOCK");
|
||||
self.merge_clipboard_deps(i);
|
||||
let name = self.unique_block_name("Block");
|
||||
let base = self.clipboard_centroid;
|
||||
let entities = self.clipboard.clone();
|
||||
match self
|
||||
.tabs[i]
|
||||
.scene
|
||||
.define_block_from_owned_entities(entities, &name, base)
|
||||
{
|
||||
Ok(()) => {
|
||||
// Block defined; now place it interactively so the
|
||||
// user picks the drop point (insertion uses the
|
||||
// clipboard centroid as the block's base). The
|
||||
// clipboard wires rubber-band under the cursor.
|
||||
self.tabs[i].dirty = true;
|
||||
let wires = self.tabs[i].scene.wires_for_entities(&self.clipboard);
|
||||
use crate::modules::insert::insert_block::InsertBlockCommand;
|
||||
let cmd = InsertBlockCommand::new_for_block(name, wires, base);
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
}
|
||||
Err(e) => self.command_line.push_error(&format!("PASTEBLOCK: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"BLOCK" => {
|
||||
let handles: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
|
||||
+8
-2
@@ -186,8 +186,14 @@ pub(super) fn entities_centroid(wires: &[WireModel]) -> glam::Vec3 {
|
||||
let mut count = 0usize;
|
||||
for w in wires {
|
||||
for p in &w.points {
|
||||
sum += glam::Vec3::from(*p);
|
||||
count += 1;
|
||||
let v = glam::Vec3::from(*p);
|
||||
// Wire models carry NaN points as separators between disjoint
|
||||
// segments; summing them poisons the whole centroid into NaN,
|
||||
// which then makes every paste base point NaN. (#129)
|
||||
if v.is_finite() {
|
||||
sum += v;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if count > 0 {
|
||||
|
||||
Reference in new issue
Block a user