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); } pub fn apply_mask(common: &mut acadrust::entities::EntityCommon, mask: u8, change_byblock: bool) -> bool { let before = common.clone(); 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; } *common != before } enum Step { Selection, Settings, ByBlock, Blocks } pub struct SetByLayerCommand { selected: Vec, step: Step, change_byblock: bool, include_blocks: bool, pending_mode: u8, } impl SetByLayerCommand { pub fn new(selected: Vec) -> Self { let step = if selected.is_empty() { Step::Selection } else { Step::ByBlock }; let pending_mode = mode(); Self { selected, step, change_byblock: pending_mode & 32 != 0, include_blocks: pending_mode & 64 != 0, pending_mode } } 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 = (self.pending_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 | Step::Settings => CmdResult::NeedPoint, } } } impl CadCommand for SetByLayerCommand { fn name(&self) -> &'static str { "SETBYLAYER" } fn prompt(&self) -> String { match self.step { Step::Selection => "Select objects or [Settings]:".into(), Step::Settings => { let active = [(1, "Color"), (2, "Linetype"), (4, "Lineweight"), (128, "Transparency"), (8, "Material")] .into_iter().filter_map(|(bit, label)| (self.pending_mode & bit != 0).then_some(label)) .collect::>().join(" "); format!("Current active settings: {active}\nEnter a property to toggle [Color/LType/LWeight/Transparency/Material]:") } 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 { match self.step { Step::Selection => vec![CmdOption::new("Settings", "S")], Step::Settings => vec![CmdOption::new("Color", "C"), CmdOption::new("LType", "LT"), CmdOption::new("LWeight", "LW"), CmdOption::new("Transparency", "T"), CmdOption::new("Material", "M")], _ => vec![CmdOption::new("Yes", "Y"), CmdOption::new("No", "N")], } } fn wants_text_input(&self) -> bool { true } fn on_point(&mut self, _: DVec3) -> CmdResult { CmdResult::NeedPoint } fn on_enter(&mut self) -> CmdResult { if matches!(self.step, Step::Settings) { self.step = Step::Selection; return CmdResult::NeedPoint; } 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 { let text = text.trim().trim_start_matches('_').to_uppercase(); if matches!(self.step, Step::Selection) { return if matches!(text.as_str(), "S" | "SETTINGS") { self.step = Step::Settings; Some(CmdResult::NeedPoint) } else { None }; } if matches!(self.step, Step::Settings) { let bit = match text.as_str() { "C" | "COLOR" => 1, "LT" | "LTYPE" => 2, "LW" | "LWEIGHT" => 4, "T" | "TRANSPARENCY" => 128, "M" | "MATERIAL" => 8, _ => return Some(CmdResult::NeedPoint), }; self.pending_mode ^= bit; return Some(CmdResult::NeedPoint); } Some(match text.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", "-SETBYLAYER"] }); #[cfg(test)] mod tests { use super::*; use acadrust::entities::EntityCommon; use acadrust::types::{Color, Transparency}; #[test] fn mask_changes_only_requested_properties() { let mut common = EntityCommon::default(); common.color = Color::from_index(3); common.linetype = "DASHED".into(); common.transparency = Transparency::from_percent(40.0); assert!(apply_mask(&mut common, 1 | 128, false)); assert_eq!(common.color, Color::ByLayer); assert_eq!(common.linetype, "DASHED"); assert_eq!(common.transparency, Transparency::BY_LAYER); } #[test] fn byblock_values_are_kept_unless_requested() { let mut common = EntityCommon::default(); common.color = Color::ByBlock; common.linetype = "ByBlock".into(); common.transparency = Transparency::BY_BLOCK; assert!(!apply_mask(&mut common, 1 | 2 | 128, false)); assert!(apply_mask(&mut common, 1 | 2 | 128, true)); assert_eq!(common.color, Color::ByLayer); assert_eq!(common.linetype, "ByLayer"); assert_eq!(common.transparency, Transparency::BY_LAYER); } #[test] fn selection_settings_are_committed_with_the_apply_answers() { set_mode(255); let mut command = SetByLayerCommand::new(Vec::new()); assert!(matches!(command.on_text_input("settings"), Some(CmdResult::NeedPoint))); assert!(matches!(command.on_text_input("color"), Some(CmdResult::NeedPoint))); assert_eq!(mode(), 255, "settings stay pending until the command applies"); assert!(matches!(command.on_enter(), CmdResult::NeedPoint)); command.on_selection_complete(vec![Handle::new(1)]); assert!(matches!(command.on_enter(), CmdResult::NeedPoint)); assert!(matches!(command.on_enter(), CmdResult::NeedPoint)); assert!(matches!(command.on_enter(), CmdResult::Relaunch(_, _))); assert_eq!(mode(), 254); set_mode(255); } }