forked from 1b-agent/opencadstudio-upstream
Merge PR #1177: Copy picked nested objects with placement and array options
This commit is contained in:
commit
bfadf1c04e
8 files changed
+77
-13
No files matched your search
@@ -1274,6 +1274,12 @@ impl OpenCADStudio {
|
||||
|
||||
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
if let Some(bind) = self.tabs[i].active_cmd.as_ref().and_then(|command| command.nested_copy_bind_setting()) {
|
||||
if self.ncopy_bind != bind {
|
||||
self.ncopy_bind = bind;
|
||||
self.persist_settings_if_changed();
|
||||
}
|
||||
}
|
||||
let preserve_commit_style = self.tabs[i].active_cmd.as_ref()
|
||||
.is_some_and(|command| command.preserve_commit_style());
|
||||
let preserve_commit_layer = self.tabs[i]
|
||||
@@ -1413,7 +1419,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
}
|
||||
CmdResult::CommitEntities(entities) => {
|
||||
CmdResult::CommitEntities(mut entities) => {
|
||||
let locked_source = entities.iter().find_map(|entity| {
|
||||
let handle = entity.common().handle;
|
||||
(!handle.is_null()
|
||||
@@ -1426,10 +1432,19 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
let delta_safe = entities
|
||||
let symbol_names = self.tabs[i].active_cmd.as_ref()
|
||||
.and_then(|command| command.nested_copy_symbol_names()).cloned();
|
||||
let delta_safe = symbol_names.is_none() && entities
|
||||
.iter()
|
||||
.all(|entity| self.delta_add_safe(i, entity));
|
||||
let pending = self.begin_undo(i, label, entities.len(), delta_safe);
|
||||
if let Some(names) = symbol_names {
|
||||
let retained = self.tabs[i].scene.document.localize_nested_copy_symbols(&mut entities, &names);
|
||||
if retained > 0 {
|
||||
self.command_line.push_info(&format!("{} copied object(s) retain imported style references whose dependencies cannot be localized.", retained));
|
||||
}
|
||||
self.refresh_layer_panel();
|
||||
}
|
||||
for entity in entities {
|
||||
if preserve_commit_style {
|
||||
let _ = self.commit_entity_handle_preserve_style(entity);
|
||||
@@ -1449,12 +1464,21 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
}
|
||||
CmdResult::CommitEntitiesAndExit(entities) => {
|
||||
CmdResult::CommitEntitiesAndExit(mut entities) => {
|
||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
let delta_safe = entities
|
||||
let symbol_names = self.tabs[i].active_cmd.as_ref()
|
||||
.and_then(|command| command.nested_copy_symbol_names()).cloned();
|
||||
let delta_safe = symbol_names.is_none() && entities
|
||||
.iter()
|
||||
.all(|entity| self.delta_add_safe(i, entity));
|
||||
let pending = self.begin_undo(i, label, entities.len(), delta_safe);
|
||||
if let Some(names) = symbol_names {
|
||||
let retained = self.tabs[i].scene.document.localize_nested_copy_symbols(&mut entities, &names);
|
||||
if retained > 0 {
|
||||
self.command_line.push_info(&format!("{} copied object(s) retain imported style references whose dependencies cannot be localized.", retained));
|
||||
}
|
||||
self.refresh_layer_panel();
|
||||
}
|
||||
for entity in entities {
|
||||
if preserve_commit_style {
|
||||
let _ = self.commit_entity_handle_preserve_style(entity);
|
||||
|
||||
@@ -656,7 +656,7 @@ impl OpenCADStudio {
|
||||
|
||||
"NCOPY" | "NCOPYALL" => {
|
||||
let command = crate::modules::draw::modify::ncopy::NcopyCommand::new(
|
||||
&self.tabs[i].scene.document,
|
||||
&self.tabs[i].scene.document, self.ncopy_bind,
|
||||
);
|
||||
self.command_line.push_info(&command.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
|
||||
@@ -509,6 +509,7 @@ pub(super) struct OpenCADStudio {
|
||||
/// Selected-object count past which grips stop being generated
|
||||
/// (GRIPOBJLIMIT, 0..=32767; 0 = no limit).
|
||||
grip_object_limit: i32,
|
||||
ncopy_bind: bool,
|
||||
/// Drawing viewport cursor style (CURSORTYPE).
|
||||
cursor_type: settings::CursorType,
|
||||
/// Explicit crosshair colour; `None` retains automatic contrast.
|
||||
@@ -3526,6 +3527,7 @@ impl OpenCADStudio {
|
||||
double_click_block_refedit: false,
|
||||
double_click_block_attedit: true,
|
||||
grip_object_limit: settings::DEFAULT_GRIP_OBJECT_LIMIT,
|
||||
ncopy_bind: false,
|
||||
cursor_type: settings::CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
crosshair_color_input: String::new(),
|
||||
|
||||
@@ -203,6 +203,8 @@ pub struct UserSettings {
|
||||
/// GRIPOBJLIMIT: past this many selected objects, no grips are drawn at
|
||||
/// all. 0 means no limit. The drawing header carries no slot for it.
|
||||
pub grip_object_limit: i32,
|
||||
/// Nested-copy symbol handling: false inserts, true binds.
|
||||
pub ncopy_bind: bool,
|
||||
/// Last Options page; unknown saved names fall back without rejecting the config.
|
||||
#[serde(default, deserialize_with = "deserialize_options_tab")]
|
||||
pub options_tab: crate::ui::window::options::OptionsTab,
|
||||
@@ -386,6 +388,7 @@ impl Default for UserSettings {
|
||||
double_click_block_refedit: false,
|
||||
double_click_block_attedit: true,
|
||||
grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT,
|
||||
ncopy_bind: false,
|
||||
cursor_type: CursorType::Crosshair,
|
||||
crosshair_color: None,
|
||||
lineweight_display_scale: 100,
|
||||
|
||||
@@ -404,6 +404,7 @@ impl OpenCADStudio {
|
||||
double_click_block_refedit: self.double_click_block_refedit,
|
||||
double_click_block_attedit: self.double_click_block_attedit,
|
||||
grip_object_limit: self.grip_object_limit,
|
||||
ncopy_bind: self.ncopy_bind,
|
||||
cursor_type: self.cursor_type,
|
||||
crosshair_color: self.crosshair_color,
|
||||
lineweight_display_scale: self.lineweight_display_scale,
|
||||
@@ -469,6 +470,7 @@ impl OpenCADStudio {
|
||||
self.double_click_block_refedit = s.double_click_block_refedit;
|
||||
self.double_click_block_attedit = s.double_click_block_attedit;
|
||||
self.grip_object_limit = s.grip_object_limit.clamp(0, 32767);
|
||||
self.ncopy_bind = s.ncopy_bind;
|
||||
self.cursor_type = s.cursor_type;
|
||||
self.crosshair_color = s.crosshair_color;
|
||||
self.crosshair_color_input = s
|
||||
|
||||
@@ -1996,6 +1996,9 @@ impl InputKind {
|
||||
pub trait CadCommand: Send {
|
||||
/// Preserve source appearance for commands that extract existing entities.
|
||||
fn preserve_commit_style(&self) -> bool { false }
|
||||
fn nested_copy_bind_setting(&self) -> Option<bool> { None }
|
||||
/// Symbol localization is committed together with the extracted entities.
|
||||
fn nested_copy_symbol_names(&self) -> Option<&acadrust::nested_copy::NestedCopySymbolNames> { None }
|
||||
/// Keep the layer already carried by entities committed by this command
|
||||
/// instead of replacing it with the current drawing layer.
|
||||
fn preserve_commit_layer(&self) -> bool {
|
||||
|
||||
@@ -347,6 +347,7 @@ fn merge_xref_into_block(
|
||||
let old = layer.name.clone();
|
||||
let new = format!("{}|{}", prefix, old);
|
||||
let mut cloned = layer.clone();
|
||||
xref_doc.normalize_imported_layer_defaults(&mut cloned);
|
||||
cloned.name = new.clone();
|
||||
cloned.set_handle(doc.allocate_handle());
|
||||
doc.layers.add_or_replace(cloned);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
use glam::DVec3;
|
||||
use acadrust::nested_copy::{NestedCopyMode, NestedCopySymbolNames};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use crate::command::{CadCommand, CmdOption, CmdResult, EntityTransform};
|
||||
use crate::entities::traits::EntityTypeOps;
|
||||
use crate::scene::convert::acad_to_render::RenderObject;
|
||||
|
||||
enum Step { Select, Base, Target(DVec3), ArrayCount(DVec3), ArrayTarget(DVec3, usize, bool) }
|
||||
enum Step { Select, Settings, Base, Target(DVec3), ArrayCount(DVec3), ArrayTarget(DVec3, usize, bool) }
|
||||
pub struct NcopyCommand {
|
||||
nested: HashMap<Handle, Vec<EntityType>>,
|
||||
hit_paths: HashMap<Handle, Vec<Vec<[f64; 3]>>>,
|
||||
@@ -13,9 +14,12 @@ pub struct NcopyCommand {
|
||||
step: Step,
|
||||
multiple: bool,
|
||||
placements: usize,
|
||||
mode: NestedCopyMode,
|
||||
insert_names: NestedCopySymbolNames,
|
||||
bind_names: NestedCopySymbolNames,
|
||||
}
|
||||
impl NcopyCommand {
|
||||
pub fn new(document: &CadDocument) -> Self {
|
||||
pub fn new(document: &CadDocument, bind: bool) -> Self {
|
||||
fn leaves(entity: &EntityType, document: &CadDocument, path: &mut HashSet<String>, output: &mut Vec<EntityType>) {
|
||||
let EntityType::Insert(insert) = entity else { output.push(entity.clone()); return; };
|
||||
let name = insert.block_name.to_ascii_uppercase();
|
||||
@@ -43,7 +47,11 @@ impl NcopyCommand {
|
||||
}).collect();
|
||||
(handle, paths)
|
||||
}).collect();
|
||||
Self { nested, hit_paths, selected: Vec::new(), step: Step::Select, multiple: false, placements: 0 }
|
||||
Self { nested, hit_paths, selected: Vec::new(), step: Step::Select, multiple: false, placements: 0,
|
||||
mode: if bind { NestedCopyMode::Bind } else { NestedCopyMode::Insert },
|
||||
insert_names: document.nested_copy_symbol_names(NestedCopyMode::Insert),
|
||||
bind_names: document.nested_copy_symbol_names(NestedCopyMode::Bind),
|
||||
}
|
||||
}
|
||||
fn place(&self, delta: DVec3) -> CmdResult {
|
||||
if !delta.is_finite() { return CmdResult::NeedPoint; }
|
||||
@@ -61,7 +69,8 @@ impl CadCommand for NcopyCommand {
|
||||
fn name(&self) -> &'static str { "NCOPY" }
|
||||
fn prompt(&self) -> String {
|
||||
match self.step {
|
||||
Step::Select => "Select nested objects:".into(),
|
||||
Step::Select => format!("Current setting: {}. Select nested objects or [Settings]:", if self.mode == NestedCopyMode::Bind { "Bind" } else { "Insert" }),
|
||||
Step::Settings => format!("Enter setting [Insert/Bind] <{}>:", if self.mode == NestedCopyMode::Bind { "Bind" } else { "Insert" }),
|
||||
Step::Base => "Specify base point or [Displacement/Multiple] <Displacement>:".into(),
|
||||
Step::Target(_) => "Specify second point or [Array] <use first point as displacement>:".into(),
|
||||
Step::ArrayCount(_) => "Enter number of items to array:".into(),
|
||||
@@ -71,6 +80,8 @@ impl CadCommand for NcopyCommand {
|
||||
}
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.step {
|
||||
Step::Select => vec![CmdOption::new("Settings", "S")],
|
||||
Step::Settings => vec![CmdOption::new("Insert", "I"), CmdOption::new("Bind", "B")],
|
||||
Step::Base => vec![CmdOption::new("Displacement", "D"), CmdOption::new("Multiple", "M")],
|
||||
Step::Target(_) if self.multiple && self.placements > 0 => vec![CmdOption::new("Array", "A"), CmdOption::new("Exit", "E"), CmdOption::new("Undo", "U")],
|
||||
Step::Target(_) => vec![CmdOption::new("Array", "A")],
|
||||
@@ -78,6 +89,10 @@ impl CadCommand for NcopyCommand {
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
fn nested_copy_bind_setting(&self) -> Option<bool> { Some(self.mode == NestedCopyMode::Bind) }
|
||||
fn nested_copy_symbol_names(&self) -> Option<&NestedCopySymbolNames> {
|
||||
Some(if self.mode == NestedCopyMode::Bind { &self.bind_names } else { &self.insert_names })
|
||||
}
|
||||
fn preserve_commit_style(&self) -> bool { true }
|
||||
fn preserve_commit_layer(&self) -> bool { true }
|
||||
fn needs_entity_pick(&self) -> bool { matches!(self.step, Step::Select) }
|
||||
@@ -109,7 +124,7 @@ impl CadCommand for NcopyCommand {
|
||||
}
|
||||
fn on_point(&mut self, point: DVec3) -> CmdResult {
|
||||
match self.step {
|
||||
Step::Select => CmdResult::NeedPoint,
|
||||
Step::Select | Step::Settings => CmdResult::NeedPoint,
|
||||
Step::Base => { self.step = Step::Target(point); CmdResult::NeedPoint }
|
||||
Step::Target(base) => {
|
||||
if !point.is_finite() { return CmdResult::NeedPoint; }
|
||||
@@ -132,15 +147,25 @@ impl CadCommand for NcopyCommand {
|
||||
}
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match self.step {
|
||||
Step::Settings => { self.step = Step::Select; CmdResult::NeedPoint }
|
||||
Step::Select if !self.selected.is_empty() => { self.step = Step::Base; CmdResult::NeedPoint }
|
||||
Step::Base => { self.step = Step::Target(DVec3::ZERO); CmdResult::NeedPoint }
|
||||
Step::Target(base) if !self.multiple => self.place(base),
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
fn point_step_accepts_keywords(&self) -> bool { matches!(self.step, Step::Base | Step::Target(_) | Step::ArrayTarget(..)) }
|
||||
fn wants_text_input(&self) -> bool { !matches!(self.step, Step::Select) }
|
||||
fn point_step_accepts_keywords(&self) -> bool { matches!(self.step, Step::Select | Step::Settings | Step::Base | Step::Target(_) | Step::ArrayTarget(..)) }
|
||||
fn wants_text_input(&self) -> bool { true }
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if matches!(self.step, Step::Settings) {
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"I" | "INSERT" => self.mode = NestedCopyMode::Insert,
|
||||
"B" | "BIND" => self.mode = NestedCopyMode::Bind,
|
||||
_ => return Some(CmdResult::NeedPoint),
|
||||
}
|
||||
self.step = Step::Select;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
if let Step::ArrayCount(base) = self.step {
|
||||
if let Ok(count) = text.trim().parse::<usize>() {
|
||||
if (2..=32767).contains(&count) { self.step = Step::ArrayTarget(base, count, false); }
|
||||
@@ -148,6 +173,7 @@ impl CadCommand for NcopyCommand {
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"S" | "SETTINGS" if matches!(self.step, Step::Select) => { self.step = Step::Settings; Some(CmdResult::NeedPoint) }
|
||||
"A" | "ARRAY" => {
|
||||
if let Step::Target(base) = self.step { self.step = Step::ArrayCount(base); }
|
||||
Some(CmdResult::NeedPoint)
|
||||
@@ -184,7 +210,10 @@ mod tests {
|
||||
let mut nested = HashMap::new();
|
||||
nested.insert(handle, vec![EntityType::Line(line)]);
|
||||
let command = NcopyCommand { nested, hit_paths: HashMap::new(), selected: vec![(handle, 0)],
|
||||
step: Step::Base, multiple: false, placements: 0 };
|
||||
step: Step::Base, multiple: false, placements: 0,
|
||||
mode: NestedCopyMode::Insert,
|
||||
insert_names: NestedCopySymbolNames::default(),
|
||||
bind_names: NestedCopySymbolNames::default() };
|
||||
|
||||
let CmdResult::CommitEntitiesAndExit(entities) = command.place(DVec3::new(3.0, 4.0, 5.0))
|
||||
else { panic!("expected copied entity") };
|
||||
|
||||
Reference in new issue
Block a user