use super::{H7CAD, Message}; use crate::command::CadCommand; use crate::scene::Scene; use iced::Task; use std::path::PathBuf; impl H7CAD { pub(super) fn dispatch_command(&mut self, cmd: &str) -> Task { let i = self.active_tab; // Cancel any running command before starting a new one. if self.tabs[i].active_cmd.is_some() { self.tabs[i].scene.clear_preview_wire(); self.tabs[i].active_cmd = None; } // Reset the last committed point so the first click of the new command // is not constrained by ortho/polar relative to a previous command's endpoint. self.last_point = None; if let Some(path_str) = cmd.strip_prefix("OPEN_RECENT:") { let path = PathBuf::from(path_str); return Task::perform(crate::io::open_path(path), Message::FileOpened); } match cmd { "NEW" => return Task::done(Message::ClearScene), "OPEN" => return Task::done(Message::OpenFile), "SAVE"|"QSAVE" => return Task::done(Message::SaveFile), "SAVEAS" => return Task::done(Message::SaveAs), "UNDO"|"U" => return Task::done(Message::Undo), "REDO" => return Task::done(Message::Redo), "CLEAR"|"CLR" => return Task::done(Message::ClearScene), "WIREFRAME"|"VW" => return Task::done(Message::SetWireframe(true)), "SOLID"|"VS" => return Task::done(Message::SetWireframe(false)), "ORTHO" => return Task::done(Message::SetProjection(true)), "PERSP" => return Task::done(Message::SetProjection(false)), "LAYERS"|"LA" => return Task::done(Message::ToggleLayers), // ── Layer object commands ────────────────────────────────────── "LAYOFF" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYOFF"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let layers: std::collections::HashSet = self.tabs[i].scene .selected_entities().into_iter() .map(|(_, e)| e.common().layer.clone()).collect(); self.push_undo_snapshot(i, "LAYOFF"); for name in &layers { if name == "0" { continue; } if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) { dl.turn_off(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("Layer(s) turned off."); } } "LAYFRZ" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYFRZ"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let layers: std::collections::HashSet = self.tabs[i].scene .selected_entities().into_iter() .map(|(_, e)| e.common().layer.clone()).collect(); self.push_undo_snapshot(i, "LAYFRZ"); for name in &layers { if name == "0" { continue; } if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) { dl.freeze(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("Layer(s) frozen."); } } "LAYLCK" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYLCK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let layers: std::collections::HashSet = self.tabs[i].scene .selected_entities().into_iter() .map(|(_, e)| e.common().layer.clone()).collect(); self.push_undo_snapshot(i, "LAYLCK"); for name in &layers { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) { dl.lock(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("Layer(s) locked."); } } "LAYMCUR" => { let entities = self.tabs[i].scene.selected_entities(); if entities.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYMCUR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let layer = entities[0].1.common().layer.clone(); self.tabs[i].active_layer = layer.clone(); self.ribbon.active_layer = layer.clone(); self.tabs[i].layers.current_layer = layer.clone(); self.command_line.push_info(&format!("Current layer set to \"{layer}\".")); self.sync_ribbon_layers(); } } "LAYON" => { self.push_undo_snapshot(i, "LAYON"); for name in self.tabs[i].scene.document.layers.iter() .map(|l| l.name.clone()).collect::>() { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(&name) { dl.turn_on(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("All layers turned on."); } "LAYTHW" => { self.push_undo_snapshot(i, "LAYTHW"); for name in self.tabs[i].scene.document.layers.iter() .map(|l| l.name.clone()).collect::>() { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(&name) { dl.thaw(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("All layers thawed."); } "LAYULK" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("LAYULK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let layers: std::collections::HashSet = self.tabs[i].scene .selected_entities().into_iter() .map(|(_, e)| e.common().layer.clone()).collect(); self.push_undo_snapshot(i, "LAYULK"); for name in &layers { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(name) { dl.unlock(); } } self.tabs[i].dirty = true; self.sync_ribbon_layers(); self.command_line.push_info("Layer(s) unlocked."); } } "LAYMATCH"|"LAYMCH" => { use crate::modules::home::layers::match_layer::LayMatchCommand; let dest: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); let cmd = LayMatchCommand::new(dest); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "MATCHPROP"|"MA" => { use crate::modules::home::properties::match_prop::MatchPropCommand; self.tabs[i].scene.deselect_all(); let cmd = MatchPropCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "GROUP"|"G" => { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("GROUP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let auto_name = super::helpers::next_group_auto_name(&self.tabs[i].scene); use crate::modules::home::groups::group::GroupCommand; let cmd = GroupCommand::new(handles, auto_name); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } } "UNGROUP"|"UG" => { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::groups::ungroup::UngroupCommand; let cmd = UngroupCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { self.push_undo_snapshot(i, "UNGROUP"); let count = self.tabs[i].scene.delete_groups_containing(&handles); self.tabs[i].dirty = true; if count > 0 { self.command_line.push_info(&format!("{} group(s) dissolved.", count)); } else { self.command_line.push_info("No groups found for selected objects."); } } } "COPYCLIP"|"CC" => { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("COPYCLIP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let entities: Vec<_> = handles.iter() .filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned()) .collect(); self.clipboard_centroid = super::helpers::entities_centroid( &self.tabs[i].scene.wire_models_for(&handles), ); self.clipboard = entities; self.command_line.push_info( &format!("{} object(s) copied to clipboard.", self.clipboard.len()), ); } } "CUTCLIP"|"CX" => { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("CUTCLIP"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let entities: Vec<_> = handles.iter() .filter_map(|&h| self.tabs[i].scene.document.get_entity(h).cloned()) .collect(); self.clipboard_centroid = super::helpers::entities_centroid( &self.tabs[i].scene.wire_models_for(&handles), ); let count = entities.len(); self.clipboard = entities; self.push_undo_snapshot(i, "CUTCLIP"); self.tabs[i].scene.erase_entities(&handles); self.tabs[i].scene.deselect_all(); self.tabs[i].dirty = true; self.refresh_properties(); self.command_line.push_info( &format!("{} object(s) cut to clipboard.", count), ); } } "PASTECLIP"|"PC" => { if self.clipboard.is_empty() { self.command_line.push_error("Clipboard is empty."); } else { let wires = self.tabs[i].scene.wires_for_entities(&self.clipboard); use crate::modules::home::clipboard::paste::PasteCommand; let cmd = PasteCommand::new(wires, self.clipboard_centroid); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } } "BLOCK" => { let handles: Vec<_> = self.tabs[i] .scene.selected_entities().into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("BLOCK"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::insert::create_block::CreateBlockCommand; let cmd = CreateBlockCommand::new(handles); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } } "INSERT" => { let blocks = self.tabs[i].scene.custom_block_names(); if blocks.is_empty() { self.command_line .push_error("No user-defined blocks found in this drawing."); } else { use crate::modules::insert::insert_block::InsertBlockCommand; let cmd = InsertBlockCommand::new(blocks); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } } // ── Draw commands ────────────────────────────────────────────── "LINE"|"L" => { use crate::modules::home::draw::line::LineCommand; let new_cmd = LineCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE"|"C" => { use crate::modules::home::draw::circle::CircleCommand; let new_cmd = CircleCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE_CD" => { use crate::modules::home::draw::circle::CircleCDCommand; let new_cmd = CircleCDCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE_2P" => { use crate::modules::home::draw::circle::Circle2PCommand; let new_cmd = Circle2PCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE_3P" => { use crate::modules::home::draw::circle::Circle3PCommand; let new_cmd = Circle3PCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE_TTR" => { use crate::modules::home::draw::circle::CircleTTRCommand; let new_cmd = CircleTTRCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.pre_cmd_tangent = Some(self.snapper.is_on(crate::snap::SnapType::Tangent)); self.snapper.enabled.insert(crate::snap::SnapType::Tangent); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "CIRCLE_TTT" => { use crate::modules::home::draw::circle::CircleTTTCommand; let new_cmd = CircleTTTCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.pre_cmd_tangent = Some(self.snapper.is_on(crate::snap::SnapType::Tangent)); self.snapper.enabled.insert(crate::snap::SnapType::Tangent); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC"|"A" => { use crate::modules::home::draw::arc::ArcCommand; let new_cmd = ArcCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_3P" => { use crate::modules::home::draw::arc::Arc3PCommand; let new_cmd = Arc3PCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SCE" => { use crate::modules::home::draw::arc::ArcSCECommand; let new_cmd = ArcSCECommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SCA" => { use crate::modules::home::draw::arc::ArcSCACommand; let new_cmd = ArcSCACommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SCL" => { use crate::modules::home::draw::arc::ArcSCLCommand; let new_cmd = ArcSCLCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SEA" => { use crate::modules::home::draw::arc::ArcSEACommand; let new_cmd = ArcSEACommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SER" => { use crate::modules::home::draw::arc::ArcSERCommand; let new_cmd = ArcSERCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_SED" => { use crate::modules::home::draw::arc::ArcSEDCommand; let new_cmd = ArcSEDCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_CSA" => { use crate::modules::home::draw::arc::ArcCSACommand; let new_cmd = ArcCSACommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARC_CSL" => { use crate::modules::home::draw::arc::ArcCSLCommand; let new_cmd = ArcCSLCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "RECT" => { use crate::modules::home::draw::shapes::RectCommand; let new_cmd = RectCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "RECT_ROT" => { use crate::modules::home::draw::shapes::RectRotCommand; let new_cmd = RectRotCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "RECT_CEN" => { use crate::modules::home::draw::shapes::RectCenCommand; let new_cmd = RectCenCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "POLY" => { use crate::modules::home::draw::shapes::PolyCommand; let new_cmd = PolyCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "POLY_C" => { use crate::modules::home::draw::shapes::PolyCCommand; let new_cmd = PolyCCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "POLY_E" => { use crate::modules::home::draw::shapes::PolyECommand; let new_cmd = PolyECommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "PLINE"|"PL" => { use crate::modules::home::draw::polyline::PlineCommand; let new_cmd = PlineCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } // ── Modify commands ──────────────────────────────────────────── "MOVE"|"M" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("MOVE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::translate::MoveCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = MoveCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "COPY"|"CO" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("COPY"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::copy::CopyCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = CopyCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "ROTATE"|"RO" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ROTATE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::rotate::RotateCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = RotateCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "POINT"|"PO" => { use crate::modules::home::draw::point::PointCommand; let new_cmd = PointCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "RAY" => { use crate::modules::home::draw::ray::RayCommand; let new_cmd = RayCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "XLINE"|"XL" => { use crate::modules::home::draw::ray::XLineCommand; let new_cmd = XLineCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "HATCH"|"H" => { use crate::modules::home::draw::hatch::HatchCommand; let outlines = self.tabs[i].scene.closed_outlines(); let new_cmd = HatchCommand::new(outlines); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "GRADIENT" => { use crate::modules::home::draw::hatch::GradientCommand; let outlines = self.tabs[i].scene.closed_outlines(); let new_cmd = GradientCommand::new(outlines); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "BOUNDARY" => { use crate::modules::home::draw::hatch::BoundaryCommand; let outlines = self.tabs[i].scene.closed_outlines(); let new_cmd = BoundaryCommand::new(outlines); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ELLIPSE"|"EL" => { use crate::modules::home::draw::ellipse::EllipseCommand; let new_cmd = EllipseCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ELLIPSE_AXIS" => { use crate::modules::home::draw::ellipse::EllipseAxisCommand; let new_cmd = EllipseAxisCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ELLIPSE_ARC" => { use crate::modules::home::draw::ellipse::EllipseArcCommand; let new_cmd = EllipseArcCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "SPLINE"|"SPL" => { use crate::modules::home::draw::spline::SplineCommand; let new_cmd = SplineCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "SCALE"|"SC" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("SCALE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::scale::ScaleCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = ScaleCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "MIRROR"|"MI" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("MIRROR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::mirror::MirrorCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = MirrorCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "ERASE"|"E" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ERASE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let n = handles.len(); self.push_undo_snapshot(i, "ERASE"); self.tabs[i].scene.erase_entities(&handles); self.tabs[i].dirty = true; self.refresh_properties(); self.command_line.push_output(&format!("{n} object(s) erased.")); } } // ── Annotate commands ────────────────────────────────────────── "TEXT"|"T"|"DT" => { use crate::modules::annotate::text::TextCommand; let new_cmd = TextCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "MTEXT"|"MT" => { use crate::modules::annotate::mtext::MTextCommand; let new_cmd = MTextCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "DIMLINEAR" => { use crate::modules::annotate::linear_dim::LinearDimensionCommand; let new_cmd = LinearDimensionCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "DIMRADIUS" => { use crate::modules::annotate::radius_dim::RadiusDimensionCommand; let new_cmd = RadiusDimensionCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "DIMANGULAR" => { use crate::modules::annotate::angular_dim::AngularDimensionCommand; let new_cmd = AngularDimensionCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "LEADER"|"LE" => { use crate::modules::annotate::leader_cmd::LeaderCommand; let new_cmd = LeaderCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "MLEADER"|"MLD" => { use crate::modules::annotate::mleader_cmd::MLeaderCommand; let new_cmd = MLeaderCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ZOOM EXTENTS"|"ZOOMEXTENTS"|"ZE" => { self.tabs[i].scene.fit_all(); self.command_line.push_output("Zoom Extents"); } "ZOOM IN"|"ZI" => { self.tabs[i].scene.zoom_camera(1.0 / 1.5); self.command_line.push_output("Zoom In"); } "ZOOM OUT"|"ZO" => { self.tabs[i].scene.zoom_camera(1.5); self.command_line.push_output("Zoom Out"); } "ZOOM WINDOW"|"ZOOM W"|"ZW" => { use crate::modules::view::zoom_window::ZoomWindowCommand; let new_cmd = ZoomWindowCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "STRETCH"|"SS" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("STRETCH"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::stretch::StretchCommand; let new_cmd = StretchCommand::new(handles); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "FILLET"|"F" => { use crate::modules::home::modify::fillet::FilletCommand; let entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned().map(|e| (h, e)) }).collect(); let all_entities: Vec<_> = entities.into_iter().map(|(_, e)| e).collect(); let new_cmd = FilletCommand::new( crate::modules::home::defaults::get_fillet_radius(), all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "ARRAY"|"AR"|"ARRAYRECT" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYRECT"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::array::ArrayRectCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = ArrayRectCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "ARRAYPOLAR" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYPOLAR"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::array::ArrayPolarCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let new_cmd = ArrayPolarCommand::new(handles, wires); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "ARRAYPATH" => { let handles: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().map(|(h, _)| h).collect(); if handles.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("ARRAYPATH"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { use crate::modules::home::modify::array::ArrayPathCommand; let wires = self.tabs[i].scene.wire_models_for(&handles); let all_entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned() }).collect(); let new_cmd = ArrayPathCommand::new(handles, wires, all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } "CHAMFER"|"CHA" => { use crate::modules::home::modify::fillet::ChamferCommand; let entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned().map(|e| (h, e)) }).collect(); let all_entities: Vec<_> = entities.into_iter().map(|(_, e)| e).collect(); let new_cmd = ChamferCommand::new( crate::modules::home::defaults::get_chamfer_dist1(), all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "EXPLODE"|"X" => { use crate::modules::home::modify::explode::explode_entity; let entities: Vec<_> = self.tabs[i].scene.selected_entities() .into_iter().collect(); if entities.is_empty() { use crate::modules::home::select::SelectObjectsCommand; let cmd = SelectObjectsCommand::new("EXPLODE"); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } else { let replacements: Vec<(acadrust::Handle, Vec)> = entities .iter() .filter_map(|(h, e)| { let pieces = explode_entity(e, &self.tabs[i].scene.document); if pieces.is_empty() { None } else { Some((*h, pieces)) } }) .collect(); let exploded = replacements.len(); if exploded > 0 { self.push_undo_snapshot(i, "EXPLODE"); } for (handle, pieces) in replacements { self.tabs[i].scene.erase_entities(&[handle]); for piece in pieces { self.tabs[i].scene.add_entity(piece); } } if exploded > 0 { self.tabs[i].dirty = true; self.refresh_properties(); self.command_line.push_output(&format!("{exploded} object(s) exploded.")); } else { self.command_line.push_info("EXPLODE: no explodable objects selected."); } } } "OFFSET"|"O" => { use crate::modules::home::modify::offset::OffsetCommand; let all_entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned() }).collect(); let new_cmd = OffsetCommand::new( crate::modules::home::defaults::get_offset_dist(), all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "TRIM"|"TR" => { use crate::modules::home::modify::trim::TrimCommand; let entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned().map(|e| (h, e)) }).collect(); let all_entities: Vec<_> = entities.into_iter().map(|(_, e)| e).collect(); let new_cmd = TrimCommand::new(all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "EXTEND"|"EX" => { use crate::modules::home::modify::trim::ExtendCommand; let entities: Vec<_> = self.tabs[i].scene.entity_wires().iter() .filter_map(|w| { let h = Scene::handle_from_wire_name(&w.name)?; self.tabs[i].scene.document.get_entity(h).cloned().map(|e| (h, e)) }).collect(); let all_entities: Vec<_> = entities.into_iter().map(|(_, e)| e).collect(); let new_cmd = ExtendCommand::new(all_entities); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } "3DORBIT"|"3O" => { self.command_line.push_info("3D Orbit: drag with right mouse button."); } // ── Selection utilities ─────────────────────────────────────── "SELECTALL"|"SA" => { use crate::scene::Scene; let handles: Vec = self.tabs[i].scene.entity_wires() .iter() .filter_map(|w| Scene::handle_from_wire_name(&w.name)) .collect(); let count = handles.len(); for h in handles { self.tabs[i].scene.select_entity(h, false); } self.command_line.push_output(&format!("SELECTALL: {} object(s) selected.", count)); self.refresh_properties(); } "DESELECT"|"DE"|"DESELALL" => { self.tabs[i].scene.deselect_all(); self.command_line.push_output("Deselected."); self.refresh_properties(); } // ── LIST — entity info ──────────────────────────────────────── "LIST" | "LI" => { let selected: Vec<_> = self.tabs[i].scene.selected_entities(); if selected.is_empty() { self.command_line.push_error("LIST: no entities selected. Select entities first."); } else { for (handle, _) in &selected { if let Some(entity) = self.tabs[i].scene.document.get_entity(*handle) { let type_name = match entity { acadrust::EntityType::Line(_) => "LINE", acadrust::EntityType::Circle(_) => "CIRCLE", acadrust::EntityType::Arc(_) => "ARC", acadrust::EntityType::LwPolyline(_) => "LWPOLYLINE", acadrust::EntityType::Polyline(_) => "POLYLINE", acadrust::EntityType::Text(_) => "TEXT", acadrust::EntityType::MText(_) => "MTEXT", acadrust::EntityType::Insert(_) => "INSERT", acadrust::EntityType::Hatch(_) => "HATCH", acadrust::EntityType::Dimension(_) => "DIMENSION", acadrust::EntityType::Viewport(_) => "VIEWPORT", acadrust::EntityType::Spline(_) => "SPLINE", acadrust::EntityType::Ellipse(_) => "ELLIPSE", acadrust::EntityType::Point(_) => "POINT", acadrust::EntityType::Ray(_) => "RAY", acadrust::EntityType::XLine(_) => "XLINE", acadrust::EntityType::Face3D(_) => "3DFACE", acadrust::EntityType::Table(_) => "TABLE", _ => "ENTITY", }; let common = entity.common(); self.command_line.push_output(&format!( "{} Layer:{} Color:{} Handle:{:X}", type_name, common.layer, common.color.index().map(|c| c.to_string()).unwrap_or_else(|| "ByLayer".to_string()), handle.value() )); } } } } // ── Break / Join ───────────────────────────────────────────────── "JOIN"|"J" => { use crate::modules::home::modify::join::JoinCommand; let cmd = JoinCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "BREAK"|"BR" => { use crate::modules::home::modify::break_cmd::BreakInteractiveCommand; let cmd = BreakInteractiveCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } // ── Inquiry ────────────────────────────────────────────────────── "DIST"|"DI" => { use crate::modules::home::inquiry::dist::DistCommand; let cmd = DistCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "ID" => { use crate::modules::home::inquiry::id::IdCommand; let cmd = IdCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "AREA" => { use crate::modules::home::inquiry::area::AreaCommand; let cmd = AreaCommand::new(); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } "HELP"|"?" => { self.command_line.push_output( "Draw: LINE CIRCLE ARC PLINE RECT POLY POINT ELLIPSE SPLINE RAY XLINE HATCH | \ Modify: MOVE COPY ROTATE SCALE MIRROR ERASE OFFSET EXTEND FILLET CHAMFER STRETCH EXPLODE TRIM BREAK JOIN | \ Array: ARRAY ARRAYRECT ARRAYPOLAR ARRAYPATH | \ Text: TEXT MTEXT LEADER MLEADER | \ Dimension: DIMLINEAR DIMANGULAR DIMRADIUS | \ Inquiry: DIST ID AREA LIST | \ View: ZOOM EXTENTS VIEW LIST/SAVE/RESTORE/DELETE | \ Layer: LAYER LIST/NEW/ON/OFF/FREEZE/THAW/LOCK/UNLOCK/COLOR/SET | \ Viewport: MVIEW VPLAYER VPORTS MS PS DRAWORDER | \ Tables: STYLE DIMSTYLE LINETYPE UCS RENAME PURGE | \ File: NEW OPEN SAVE SAVEAS PRINT PURGE UNDO REDO" ); } "DONATE" => { let _ = open::that("https://patreon.com/HakanSeven12"); self.command_line.push_info("Opening Patreon page..."); } // ── Layout / viewport ────────────────────────────────────────── "MVIEW"|"MV" => { if self.tabs[i].scene.current_layout == "Model" { self.command_line.push_error("MVIEW: switch to a paper space layout first."); } else { use crate::modules::layout::mview::MviewCommand; let new_cmd = MviewCommand::new(); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } // ── MSPACE / PSPACE ─────────────────────────────────────────── "MS"|"MSPACE" => { return Task::done(Message::MspaceCommand); } "PSPACE" => { return Task::done(Message::PspaceCommand); } // ── VPORTS — list viewports in current layout ───────────────── "VPORTS" => { let scene = &self.tabs[i].scene; if scene.current_layout == "Model" { self.command_line.push_error("VPORTS: switch to a paper space layout first."); } else { let layout_block = scene.current_layout_block_handle_pub(); let viewports: Vec<_> = scene.document.entities() .filter_map(|e| { if let acadrust::EntityType::Viewport(vp) = e { if vp.id > 1 && vp.common.owner_handle == layout_block { Some((vp.id, vp.center.clone(), vp.width, vp.height, vp.custom_scale, vp.status.is_on, vp.status.locked)) } else { None } } else { None } }) .collect(); if viewports.is_empty() { self.command_line.push_info("No viewports in current layout. Use MVIEW to create one."); } else { self.command_line.push_output(&format!("{} viewport(s) in layout \"{}\":", viewports.len(), scene.current_layout)); for (id, center, w, h, scale, is_on, locked) in &viewports { let state = match (is_on, locked) { (true, true) => "On, Locked", (true, false) => "On", (false, _) => "Off", }; self.command_line.push_output(&format!( " VP #{id}: {w:.1}×{h:.1} @ ({:.1},{:.1}) scale={scale:.4} [{state}]", center.x, center.y )); } } } } // ── VPLAYER — per-viewport layer freeze/thaw ────────────────── "VPLAYER" => { let scene = &self.tabs[i].scene; if scene.current_layout == "Model" { self.command_line.push_error("VPLAYER: switch to a paper space layout first."); } else if scene.active_viewport.is_none() { self.command_line.push_error("VPLAYER: enter a viewport first (double-click or MS)."); } else { use crate::modules::layout::vplayer::VplayerCommand; let vp_handle = scene.active_viewport.unwrap(); // Collect current frozen layer names for display. let frozen_names: Vec = { if let Some(acadrust::EntityType::Viewport(vp)) = scene.document.get_entity(vp_handle) { vp.frozen_layers.iter().filter_map(|h| { scene.document.layers.iter().find(|l| l.handle == *h).map(|l| l.name.clone()) }).collect() } else { vec![] } }; if frozen_names.is_empty() { self.command_line.push_info("VPLAYER: no frozen layers in active viewport."); } else { self.command_line.push_info(&format!( "VPLAYER: frozen layers: {}", frozen_names.join(", ") )); } let new_cmd = VplayerCommand::new(vp_handle); self.command_line.push_info(&new_cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } } // ── Draw Order ──────────────────────────────────────────────── cmd if cmd.starts_with("DRAWORDER") => { use acadrust::objects::{ObjectType, SortEntitiesTable}; let option = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase(); let bring_front = match option.as_str() { "F" | "FRONT" => Some(true), "B" | "BACK" => Some(false), _ => None, }; let i = self.active_tab; let selected: Vec = self.tabs[i].scene .selected_entities() .iter() .map(|(h, _)| *h) .collect(); if selected.is_empty() { self.command_line.push_error("DRAWORDER: select entities first."); } else if let Some(to_front) = bring_front { self.push_undo_snapshot(i, "DRAWORDER"); let block_handle = self.tabs[i].scene.current_layout_block_handle_pub(); let doc = &mut self.tabs[i].scene.document; let table_handle = doc.objects.iter() .find_map(|(h, obj)| { if let ObjectType::SortEntitiesTable(t) = obj { if t.block_owner_handle == block_handle { Some(*h) } else { None } } else { None } }); if let Some(th) = table_handle { if let Some(ObjectType::SortEntitiesTable(table)) = doc.objects.get_mut(&th) { for h in &selected { if to_front { table.bring_to_front(*h); } else { table.send_to_back(*h); } } } } else { let new_handle = acadrust::Handle::new(doc.next_handle()); let mut table = SortEntitiesTable::for_block(block_handle); table.handle = new_handle; for h in &selected { if to_front { table.bring_to_front(*h); } else { table.send_to_back(*h); } } doc.objects.insert(new_handle, ObjectType::SortEntitiesTable(table)); } self.tabs[i].dirty = true; let dir = if to_front { "front" } else { "back" }; self.command_line.push_info(&format!( "DRAWORDER: moved {} entities to {}.", selected.len(), dir )); } else { self.command_line.push_info("Usage: DRAWORDER F (front) or DRAWORDER B (back)"); } } // ── LAYER management ───────────────────────────────────────── cmd if cmd == "LAYER" || cmd.starts_with("LAYER ") || cmd.starts_with("LA ") => { use acadrust::tables::Layer; let raw_rest = if cmd.starts_with("LAYER ") { cmd.trim_start_matches("LAYER ").trim() } else if cmd.starts_with("LA ") { cmd.trim_start_matches("LA ").trim() } else { "" }; let parts: Vec<&str> = raw_rest.split_whitespace().collect(); let sub = parts.get(0).map(|s| s.to_uppercase()).unwrap_or_default(); match sub.as_str() { "" | "LIST" | "?" => { let info: Vec = self.tabs[i].scene.document.layers.iter().map(|l| { let state = if l.flags.frozen { "frozen" } else if l.flags.off { "off" } else if l.flags.locked { "locked" } else { "on" }; format!("{}({})", l.name, state) }).collect(); self.command_line.push_output(&format!("Layers: {}", info.join(", "))); } "NEW" | "N" => { let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: LAYER NEW "); } else if self.tabs[i].scene.document.layers.contains(&name) { self.command_line.push_error(&format!("LAYER: '{}' already exists.", name)); } else { let layer = Layer::new(&name); let _ = self.tabs[i].scene.document.layers.add(layer); self.push_undo_snapshot(i, "LAYER NEW"); self.tabs[i].dirty = true; self.command_line.push_output(&format!("LAYER: '{}' created.", name)); } } "ON" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.off = false; l.flags.frozen = false; } } self.push_undo_snapshot(i, "LAYER ON"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers turned on."); } "OFF" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.off = true; } } self.push_undo_snapshot(i, "LAYER OFF"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers turned off."); } "FREEZE" | "FR" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.frozen = true; } } self.push_undo_snapshot(i, "LAYER FREEZE"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers frozen."); } "THAW" | "TH" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.frozen = false; } } self.push_undo_snapshot(i, "LAYER THAW"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers thawed."); } "LOCK" | "LO" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.locked = true; } } self.push_undo_snapshot(i, "LAYER LOCK"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers locked."); } "UNLOCK" | "UL" => { for name in &parts[1..] { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(name) { l.flags.locked = false; } } self.push_undo_snapshot(i, "LAYER UNLOCK"); self.tabs[i].dirty = true; self.command_line.push_output("LAYER: layers unlocked."); } "COLOR" | "C" => { // LAYER COLOR let layer_name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); let color_str = parts.get(2).map(|s| s.trim()).unwrap_or(""); if let Ok(idx) = color_str.parse::() { if let Some(l) = self.tabs[i].scene.document.layers.get_mut(&layer_name) { l.color = acadrust::types::Color::from_index(idx); self.push_undo_snapshot(i, "LAYER COLOR"); self.tabs[i].dirty = true; self.command_line.push_output(&format!("LAYER: '{}' color set to ACI {}.", layer_name, idx)); } else { self.command_line.push_error(&format!("LAYER: '{}' not found.", layer_name)); } } else { self.command_line.push_error("Usage: LAYER COLOR "); } } "SET" | "S" | "CURRENT" => { let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); if self.tabs[i].scene.document.layers.contains(&name) { self.tabs[i].layers.current_layer = name.clone(); self.command_line.push_output(&format!("LAYER: current layer set to '{}'.", name)); } else { self.command_line.push_error(&format!("LAYER: '{}' not found.", name)); } } _ => { self.command_line.push_info( "Usage: LAYER LIST | NEW | ON/OFF/FREEZE/THAW/LOCK/UNLOCK | COLOR | SET " ); } } } // ── UCS management ─────────────────────────────────────────── cmd if cmd == "UCS" || cmd.starts_with("UCS ") => { use acadrust::tables::Ucs; let parts: Vec<&str> = cmd.splitn(3, ' ').collect(); let sub = parts.get(1).map(|s| s.to_uppercase()).unwrap_or_default(); match sub.as_str() { "" | "LIST" | "?" => { let names: Vec = self.tabs[i].scene.document .ucss.iter().map(|u| u.name.clone()).collect(); if names.is_empty() { self.command_line.push_output("No named UCSs defined."); } else { self.command_line.push_output(&format!("UCSs: {}", names.join(", "))); } } "SAVE" | "S" => { let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: UCS SAVE "); } else { // Save as WCS (identity) since we don't have active UCS state yet let ucs = Ucs::new(&name); self.tabs[i].scene.document.ucss.add_or_replace(ucs); self.tabs[i].dirty = true; self.command_line.push_output(&format!("UCS '{}' saved (WCS).", name)); } } "DELETE" | "DEL" | "D" => { let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: UCS DELETE "); } else if self.tabs[i].scene.document.ucss.remove(&name).is_some() { self.tabs[i].dirty = true; self.command_line.push_output(&format!("UCS '{}' deleted.", name)); } else { self.command_line.push_error(&format!("UCS '{}' not found.", name)); } } "W" | "WORLD" => { // Reset active UCS to WCS — currently just an informational message // as full UCS integration awaits WCS↔UCS transform pipeline self.command_line.push_output("UCS reset to World Coordinate System."); } _ => { // UCS — try as a restore/apply shortcut let name = sub.clone(); if self.tabs[i].scene.document.ucss.get(&name).is_some() { self.command_line.push_output(&format!("UCS '{}' is defined. (Full UCS activation pending transform pipeline.)", name)); } else { self.command_line.push_error( "Usage: UCS LIST | UCS SAVE | UCS DELETE | UCS W" ); } } } } // ── Named Views (VIEW command) ──────────────────────────────── cmd if cmd == "VIEW" || cmd.starts_with("VIEW ") => { let parts: Vec<&str> = cmd.splitn(3, ' ').collect(); let sub = parts.get(1).map(|s| s.to_uppercase()).unwrap_or_default(); match sub.as_str() { "" | "LIST" | "?" => { let views: Vec = self.tabs[i].scene.document .views.iter().map(|v| v.name.clone()).collect(); if views.is_empty() { self.command_line.push_output("No named views saved."); } else { self.command_line.push_output(&format!( "Named views: {}", views.join(", ") )); } } "SAVE" | "S" => { let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: VIEW SAVE "); } else { let new_view = self.tabs[i].scene.current_as_named_view(&name); self.tabs[i].scene.document.views.add_or_replace(new_view); self.command_line.push_output(&format!("View '{}' saved.", name)); } } "DELETE" | "DEL" | "D" => { let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: VIEW DELETE "); } else { if self.tabs[i].scene.document.views.remove(&name).is_some() { self.command_line.push_output(&format!("View '{}' deleted.", name)); } else { self.command_line.push_error(&format!("View '{}' not found.", name)); } } } "RESTORE" | "R" => { let name = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: VIEW RESTORE "); } else { let found = self.tabs[i].scene.document.views.get(&name).cloned(); if let Some(v) = found { self.tabs[i].scene.restore_named_view(&v); self.command_line.push_output(&format!("View '{}' restored.", v.name)); } else { self.command_line.push_error(&format!("View '{}' not found.", name)); } } } // VIEW shortcut for restore _ => { let name = sub.clone(); let found = self.tabs[i].scene.document.views.get(&name).cloned(); if let Some(v) = found { self.tabs[i].scene.restore_named_view(&v); self.command_line.push_output(&format!("View '{}' restored.", v.name)); } else { self.command_line.push_error( "Usage: VIEW LIST | VIEW SAVE | VIEW RESTORE | VIEW DELETE " ); } } } } // ── DimStyle management ─────────────────────────────────────── cmd if cmd == "DIMSTYLE" || cmd == "DDIM" || cmd.starts_with("DIMSTYLE ") || cmd.starts_with("DDIM ") => { use acadrust::tables::DimStyle; let raw_rest = cmd.split_once(' ').map(|(_, r)| r.trim()).unwrap_or(""); let parts: Vec<&str> = raw_rest.split_whitespace().collect(); let sub = parts.get(0).map(|s| s.to_uppercase()).unwrap_or_default(); match sub.as_str() { "" | "LIST" | "?" => { let styles: Vec = self.tabs[i].scene.document .dim_styles.iter() .map(|s| format!("{}(txt:{:.2} asz:{:.2})", s.name, s.dimtxt, s.dimasz)) .collect(); if styles.is_empty() { self.command_line.push_output("No dim styles defined."); } else { self.command_line.push_output(&format!("DimStyles: {}", styles.join(", "))); } } "NEW" | "N" => { let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error("Usage: DIMSTYLE NEW "); } else if self.tabs[i].scene.document.dim_styles.contains(&name) { self.command_line.push_error(&format!("DIMSTYLE: '{}' already exists.", name)); } else { let style = DimStyle::new(&name); let _ = self.tabs[i].scene.document.dim_styles.add(style); self.push_undo_snapshot(i, "DIMSTYLE NEW"); self.tabs[i].dirty = true; self.command_line.push_output(&format!("DIMSTYLE: '{}' created.", name)); } } "SET" | "S" => { // DIMSTYLE SET // e.g. DIMSTYLE SET Standard dimtxt 2.5 let style_name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); let prop = parts.get(2).map(|s| s.to_lowercase()).unwrap_or_default(); let val_str = parts.get(3).map(|s| s.trim()).unwrap_or(""); if let Ok(val) = val_str.parse::() { if let Some(ds) = self.tabs[i].scene.document.dim_styles.get_mut(&style_name) { match prop.as_str() { "dimtxt" => { ds.dimtxt = val; } "dimasz" => { ds.dimasz = val; } "dimdli" => { ds.dimdli = val; } "dimexo" => { ds.dimexo = val; } "dimexe" => { ds.dimexe = val; } "dimgap" => { ds.dimgap = val; } "dimscale"| "dimlfac" => { ds.dimgap = val; } // best effort _ => { self.command_line.push_error(&format!("DIMSTYLE: unknown property '{}'. Try: dimtxt dimasz dimdli dimexo dimexe dimgap", prop)); return Task::none(); } } self.push_undo_snapshot(i, "DIMSTYLE SET"); self.tabs[i].dirty = true; self.command_line.push_output(&format!("DIMSTYLE: '{style_name}'.{prop} = {val:.3}")); } else { self.command_line.push_error(&format!("DIMSTYLE: '{}' not found.", style_name)); } } else { self.command_line.push_error("Usage: DIMSTYLE SET "); } } _ => { self.command_line.push_info( "Usage: DIMSTYLE LIST | NEW | SET " ); } } } // ── TextStyle / Style management ────────────────────────────── cmd if cmd == "STYLE" || cmd == "TEXTSTYLE" || cmd.starts_with("STYLE ") || cmd.starts_with("TEXTSTYLE ") => { let (prefix, rest) = if cmd.starts_with("TEXTSTYLE") { ("TEXTSTYLE", cmd.trim_start_matches("TEXTSTYLE").trim()) } else { ("STYLE", cmd.trim_start_matches("STYLE").trim()) }; let parts: Vec<&str> = rest.splitn(3, ' ').collect(); let sub = parts.get(0).map(|s| s.to_uppercase()).unwrap_or_default(); match sub.as_str() { "" | "LIST" | "?" => { let styles: Vec = self.tabs[i].scene.document .text_styles.iter() .map(|s| format!("{} (font: {}, w: {:.2}, oblique: {:.1}°)", s.name, s.font_file, s.width_factor, s.oblique_angle.to_degrees())) .collect(); if styles.is_empty() { self.command_line.push_output("No text styles defined."); } else { self.command_line.push_output(&format!("Text styles: {}", styles.join(" | "))); } } "SET" | "S" => { // STYLE SET — set active text style (for future text commands) let name = parts.get(1).map(|s| s.trim()).unwrap_or(""); if self.tabs[i].scene.document.text_styles.get(name).is_some() { self.command_line.push_output(&format!("{prefix}: active style set to '{name}'.")); } else { self.command_line.push_error(&format!("{prefix}: style '{name}' not found.")); } } "NEW" | "N" => { let name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); if name.is_empty() { self.command_line.push_error(&format!("Usage: {prefix} NEW ")); } else if self.tabs[i].scene.document.text_styles.contains(&name) { self.command_line.push_error(&format!("{prefix}: style '{name}' already exists.")); } else { let style = acadrust::tables::TextStyle::new(&name); let _ = self.tabs[i].scene.document.text_styles.add(style); self.push_undo_snapshot(i, "STYLE NEW"); self.tabs[i].dirty = true; self.command_line.push_output(&format!("{prefix}: style '{name}' created.")); } } "FONT" | "F" => { // STYLE FONT let style_name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string(); let font = parts.get(2).map(|s| s.trim()).unwrap_or("").to_string(); if style_name.is_empty() || font.is_empty() { self.command_line.push_error(&format!("Usage: {prefix} FONT