diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index b99da19b4..0ac9061ae 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -645,14 +645,56 @@ impl OpenCADStudio { // ── SETBYLAYER — clear color/linetype/lineweight overrides ──── // Resets the selected entities' direct property overrides back to // ByLayer so they follow their layer again. + "SETBYLAYERMODE" => { + let command = crate::modules::draw::modify::setbylayer::ModeCommand; + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + value if value.starts_with("SETBYLAYERMODE ") => { + if let Ok(mode) = value.trim_start_matches("SETBYLAYERMODE ").trim().parse::() { + crate::modules::draw::modify::setbylayer::set_mode(mode); + } else { self.command_line.push_error("SETBYLAYERMODE requires an integer from 0 to 255."); } + } "SETBYLAYER" => { - let handles: Vec<_> = self.tabs[i] + let command = crate::modules::draw::modify::setbylayer::SetByLayerCommand::new( + self.tabs[i].scene.selected.iter().copied().collect(), + ); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + cmd if cmd.starts_with("SETBYLAYER_APPLY ") => { + let flags: Vec<_> = cmd.split_whitespace().skip(1).collect(); + if flags.len() != 2 || flags.iter().any(|flag| !matches!(*flag, "0" | "1")) { + return Some(Task::none()); + } + let mask = crate::modules::draw::modify::setbylayer::mode(); + let change_byblock = flags[0] == "1"; + let include_blocks = flags[1] == "1"; + let mut handles: Vec<_> = self.tabs[i] .scene .selected_entities() .into_iter() .map(|(h, _)| h) .filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle)) .collect(); + if include_blocks { + let mut visited: std::collections::HashSet<_> = handles.iter().copied().collect(); + let mut index = 0; + while index < handles.len() { + let children = match self.tabs[i].scene.document.get_entity(handles[index]) { + Some(acadrust::EntityType::Insert(insert)) => self.tabs[i].scene.document + .block_records.get(&insert.block_name) + .map(|block| block.entity_handles.clone()).unwrap_or_default(), + _ => Vec::new(), + }; + for child in children { + if visited.insert(child) && !self.tabs[i].scene.is_layer_locked(child) { + handles.push(child); + } + } + index += 1; + } + } if handles.is_empty() { self.command_line .push_error(crate::t!("SETBYLAYER: select entities first.").as_ref()); @@ -662,11 +704,29 @@ impl OpenCADStudio { for handle in &handles { if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(*handle) { let common = entity.common_mut(); - common.color = acadrust::types::Color::ByLayer; - common.color_name = None; - common.color_book_handle = None; - common.linetype = "ByLayer".to_string(); - common.line_weight = acadrust::types::LineWeight::ByLayer; + if mask & 1 != 0 && (change_byblock || common.color != acadrust::types::Color::ByBlock) { + common.color = acadrust::types::Color::ByLayer; + common.color_name = None; + common.color_book_handle = None; + } + if mask & 2 != 0 && (change_byblock || !common.linetype.eq_ignore_ascii_case("ByBlock")) { + common.linetype = "ByLayer".to_string(); + common.linetype_handle = None; + } + if mask & 4 != 0 && (change_byblock || common.line_weight != acadrust::types::LineWeight::ByBlock) { + common.line_weight = acadrust::types::LineWeight::ByLayer; + } + if mask & 8 != 0 && (change_byblock || common.material_flags != 1) { + common.material_flags = 0; + common.material_handle = None; + } + if mask & 16 != 0 && (change_byblock || common.plotstyle_flags != 1) { + common.plotstyle_flags = 0; + common.plotstyle_handle = None; + } + if mask & 128 != 0 && (change_byblock || common.transparency != acadrust::types::Transparency::BY_BLOCK) { + common.transparency = acadrust::types::Transparency::BY_LAYER; + } changed += 1; } } diff --git a/src/modules/draw/modify/mod.rs b/src/modules/draw/modify/mod.rs index a497c4c4e..657807fd4 100644 --- a/src/modules/draw/modify/mod.rs +++ b/src/modules/draw/modify/mod.rs @@ -20,6 +20,7 @@ pub mod refedit; pub mod reverse; pub mod rotate; pub mod scale; +pub mod setbylayer; pub mod spline_ops; pub mod splinedit; pub mod stretch; diff --git a/src/modules/draw/modify/setbylayer.rs b/src/modules/draw/modify/setbylayer.rs new file mode 100644 index 000000000..6380a9c0b --- /dev/null +++ b/src/modules/draw/modify/setbylayer.rs @@ -0,0 +1,90 @@ +use acadrust::Handle; +use glam::DVec3; +use crate::command::{CadCommand, CmdOption, CmdResult}; + +static MODE: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(255); +pub fn mode() -> u8 { MODE.load(std::sync::atomic::Ordering::Relaxed) } +pub fn set_mode(value: u8) { MODE.store(value, std::sync::atomic::Ordering::Relaxed); } +enum Step { Selection, ByBlock, Blocks } + +pub struct SetByLayerCommand { + selected: Vec, + step: Step, + change_byblock: bool, + include_blocks: bool, +} + +impl SetByLayerCommand { + pub fn new(selected: Vec) -> Self { + let step = if selected.is_empty() { Step::Selection } else { Step::ByBlock }; + Self { selected, step, change_byblock: mode() & 32 != 0, include_blocks: mode() & 64 != 0 } + } + + fn answer(&mut self, yes: bool) -> CmdResult { + match self.step { + Step::ByBlock => { + self.change_byblock = yes; + self.step = Step::Blocks; + CmdResult::NeedPoint + } + Step::Blocks => { + let flags = (mode() & !(32 | 64)) | if self.change_byblock { 32 } else { 0 } | if yes { 64 } else { 0 }; + set_mode(flags); + CmdResult::Relaunch( + format!("SETBYLAYER_APPLY {} {}", u8::from(self.change_byblock), u8::from(yes)), + self.selected.clone(), + ) }, + Step::Selection => CmdResult::NeedPoint, + } + } +} + +impl CadCommand for SetByLayerCommand { + fn name(&self) -> &'static str { "SETBYLAYER" } + fn prompt(&self) -> String { + match self.step { + Step::Selection => "Select objects:".into(), + Step::ByBlock => format!("Change ByBlock to ByLayer? [Yes/No] <{}>:", if self.change_byblock { "Yes" } else { "No" }), + Step::Blocks => format!("Include blocks? [Yes/No] <{}>:", if self.include_blocks { "Yes" } else { "No" }), + } + } + fn is_selection_gathering(&self) -> bool { matches!(self.step, Step::Selection) } + fn on_selection_complete(&mut self, handles: Vec) -> CmdResult { + self.selected = handles; + CmdResult::NeedPoint + } + fn options(&self) -> Vec { + if self.is_selection_gathering() { Vec::new() } + else { vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")] } + } + fn wants_text_input(&self) -> bool { !self.is_selection_gathering() } + fn on_point(&mut self, _: DVec3) -> CmdResult { CmdResult::NeedPoint } + fn on_enter(&mut self) -> CmdResult { + if self.is_selection_gathering() { + if self.selected.is_empty() { CmdResult::Cancel } + else { self.step = Step::ByBlock; CmdResult::NeedPoint } + } else { self.answer(if matches!(self.step, Step::ByBlock) { self.change_byblock } else { self.include_blocks }) } + } + fn on_text_input(&mut self, text: &str) -> Option { + Some(match text.trim().to_uppercase().as_str() { + "Y" | "YES" => self.answer(true), + "N" | "NO" => self.answer(false), + _ => CmdResult::NeedPoint, + }) + } +} + +pub struct ModeCommand; +impl CadCommand for ModeCommand { + fn name(&self) -> &'static str { "SETBYLAYERMODE" } + fn prompt(&self) -> String { format!("Enter new value for SETBYLAYERMODE <{}>:", mode()) } + fn wants_text_input(&self) -> bool { true } + fn on_point(&mut self, _: DVec3) -> CmdResult { CmdResult::NeedPoint } + fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel } + fn on_text_input(&mut self, text: &str) -> Option { + Some(if let Ok(value) = text.trim().parse::() { + set_mode(value); CmdResult::Cancel + } else { CmdResult::NeedPoint }) + } +} +inventory::submit!(crate::command::CommandRegistration { names: &["SETBYLAYERMODE"] });