use super::*; impl OpenCADStudio { pub(super) fn dispatch_layers(&mut self, cmd: &str, i: usize) -> Option> { match cmd { // ── 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::draw::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: rustc_hash::FxHashSet = 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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); 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::draw::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: rustc_hash::FxHashSet = 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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_info("Layer(s) frozen."); } } // LAYDEL — delete a layer and erase the objects on it. cmd if cmd == "LAYDEL" || cmd.starts_with("LAYDEL ") => { let name = cmd.trim_start_matches("LAYDEL").trim(); if name.is_empty() { self.command_line.push_info("Usage: LAYDEL "); return Some(Task::none()); } let resolved = self.tabs[i] .scene .document .layers .names() .find(|k| k.eq_ignore_ascii_case(name)) .map(|s| s.to_string()); let Some(layer) = resolved else { self.command_line .push_error(&format!("LAYDEL: no layer named \"{name}\".")); return Some(Task::none()); }; if layer == "0" { self.command_line .push_error("LAYDEL: layer \"0\" cannot be deleted."); return Some(Task::none()); } if layer.eq_ignore_ascii_case(&self.tabs[i].active_layer) { self.command_line.push_error( "LAYDEL: cannot delete the current layer. Make another layer current first.", ); return Some(Task::none()); } let handles: Vec = self.tabs[i] .scene .document .entities() .filter(|e| e.common().layer == layer) .map(|e| e.common().handle) .collect(); self.push_undo_snapshot(i, "LAYDEL"); let n = handles.len(); if !handles.is_empty() { self.tabs[i].scene.erase_entities(&handles); } self.tabs[i].scene.document.layers.remove(&layer); self.tabs[i].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_output(&format!( "LAYDEL: deleted layer \"{layer}\" and {n} object(s)." )); } // LAYMRG — move every object from onto // , then delete the emptied layer. cmd if cmd == "LAYMRG" || cmd.starts_with("LAYMRG ") => { let rest = cmd.trim_start_matches("LAYMRG").trim(); let parts: Vec<&str> = rest.split_whitespace().collect(); if parts.len() != 2 { self.command_line .push_info("Usage: LAYMRG "); return Some(Task::none()); } let keys: Vec = self.tabs[i] .scene .document .layers .names() .map(|s| s.to_string()) .collect(); let src = keys .iter() .find(|k| k.eq_ignore_ascii_case(parts[0])) .cloned(); let dst = keys .iter() .find(|k| k.eq_ignore_ascii_case(parts[1])) .cloned(); let (Some(src), Some(dst)) = (src, dst) else { self.command_line .push_error("LAYMRG: source and target layers must both exist."); return Some(Task::none()); }; if src == dst { self.command_line .push_error("LAYMRG: source and target are the same layer."); return Some(Task::none()); } if src == "0" { self.command_line .push_error("LAYMRG: layer \"0\" cannot be merged away."); return Some(Task::none()); } if src.eq_ignore_ascii_case(&self.tabs[i].active_layer) { self.command_line.push_error( "LAYMRG: cannot merge the current layer. Make another layer current first.", ); return Some(Task::none()); } self.push_undo_snapshot(i, "LAYMRG"); let mut moved = 0usize; for e in self.tabs[i].scene.document.entities_mut() { if e.common().layer == src { e.common_mut().layer = dst.clone(); moved += 1; } } self.tabs[i].scene.document.layers.remove(&src); self.tabs[i].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_output(&format!( "LAYMRG: merged \"{src}\" into \"{dst}\" ({moved} object(s))." )); } // LAYERSTATE — save / restore named snapshots of all layer states // (on/off, freeze, lock, colour, linetype, lineweight). // LAYERSTATE SAVE | RESTORE | DELETE | ? (list) cmd if cmd == "LAYERSTATE" || cmd == "LAS" || cmd == "LMAN" || cmd.starts_with("LAYERSTATE ") || cmd.starts_with("LAS ") || cmd.starts_with("LMAN ") => { let rest = cmd .trim_start_matches("LAYERSTATE") .trim_start_matches("LMAN") .trim_start_matches("LAS") .trim(); let mut parts = rest.splitn(2, char::is_whitespace); let sub = parts.next().unwrap_or("").to_uppercase(); let arg = parts.next().unwrap_or("").trim(); match sub.as_str() { "" | "?" | "LIST" => { let states = &self.tabs[i].layer_states; if states.is_empty() { self.command_line.push_info( "LAYERSTATE: no saved states. Use LAYERSTATE SAVE .", ); } else { let mut names: Vec<&str> = states.keys().map(|s| s.as_str()).collect(); names.sort_unstable(); self.command_line .push_output(&format!("Saved layer states: {}", names.join(", "))); } } "SAVE" | "S" => { if arg.is_empty() { self.command_line.push_info("Usage: LAYERSTATE SAVE "); } else { self.tabs[i].save_layer_state(arg); self.command_line .push_output(&format!("LAYERSTATE: saved \"{arg}\".")); } } "RESTORE" | "R" => { if arg.is_empty() { self.command_line .push_info("Usage: LAYERSTATE RESTORE "); } else if !self.tabs[i].layer_states.contains_key(arg) { self.command_line.push_error(&format!( "LAYERSTATE: no saved state named \"{arg}\"." )); } else { self.push_undo_snapshot(i, "LAYERSTATE"); let n = self.tabs[i].restore_layer_state(arg).unwrap_or(0); self.tabs[i].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_output(&format!( "LAYERSTATE: restored \"{arg}\" ({n} layer(s))." )); } } "DELETE" | "D" => { if self.tabs[i].layer_states.remove(arg).is_some() { self.command_line .push_output(&format!("LAYERSTATE: deleted \"{arg}\".")); } else { self.command_line.push_error(&format!( "LAYERSTATE: no saved state named \"{arg}\"." )); } } _ => { self.command_line .push_info("Usage: LAYERSTATE SAVE|RESTORE|DELETE | ? (list)"); } } } "LAYLCK" => { let handles: Vec<_> = self.tabs[i] .scene .selected_entities() .into_iter() .map(|(h, _)| h) .collect(); if handles.is_empty() { use crate::modules::draw::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: rustc_hash::FxHashSet = 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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_info("Layer(s) locked."); } } "LAYMCUR" => { let entities = self.tabs[i].scene.selected_entities(); if entities.is_empty() { use crate::modules::draw::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(); // Keep the document header (CLAYER) in sync, not just the // per-tab default, so a later no-selection ribbon refresh // (e.g. after Esc) doesn't snap back to the stale header // layer. See #93. let handle = self.tabs[i] .scene .document .layers .get(&layer) .map(|l| l.handle) .unwrap_or(acadrust::types::Handle::NULL); self.tabs[i].scene.document.header.current_layer_name = layer.clone(); self.tabs[i].scene.document.header.current_layer_handle = handle; self.tabs[i].active_layer = layer.clone(); self.ribbon.active_layer = layer.clone(); self.tabs[i].layers.current_layer = layer.clone(); self.tabs[i].dirty = true; self.command_line .push_info(&format!("Current layer set to \"{layer}\".")); self.refresh_layer_panel(); } } "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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); 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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); 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::draw::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: rustc_hash::FxHashSet = 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].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line.push_info("Layer(s) unlocked."); } } // LAYISO — turn off all layers except those used by selected entities "LAYISO" => { let sel_layers: rustc_hash::FxHashSet = self.tabs[i] .scene .selected_entities() .into_iter() .map(|(_, e)| e.common().layer.clone()) .collect(); if sel_layers.is_empty() { self.command_line .push_error("LAYISO: select entities on the layers to isolate first."); } else { self.push_undo_snapshot(i, "LAYISO"); let names: Vec = self.tabs[i] .scene .document .layers .iter() .map(|l| l.name.clone()) .collect(); for name in names { if !sel_layers.contains(&name) { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(&name) { dl.turn_off(); } } } self.tabs[i].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line .push_info(&format!("LAYISO: isolated {} layer(s).", sel_layers.len())); } } // ISOLATEOBJECTS — hide every object except the current selection "ISOLATEOBJECTS" => { if self.tabs[i].scene.selected.is_empty() { self.command_line .push_error("ISOLATEOBJECTS: select the objects to isolate first."); } else { let n = self.tabs[i].scene.selected.len(); self.tabs[i].scene.isolate_selected(); self.command_line.push_info(&format!( "Isolated {n} object(s). UNISOLATEOBJECTS to restore." )); } } // HIDEOBJECTS — hide the current selection "HIDEOBJECTS" => { if self.tabs[i].scene.selected.is_empty() { self.command_line .push_error("HIDEOBJECTS: select the objects to hide first."); } else { let n = self.tabs[i].scene.selected.len(); self.tabs[i].scene.hide_selected(); self.command_line .push_info(&format!("Hid {n} object(s). UNISOLATEOBJECTS to restore.")); } } // UNISOLATEOBJECTS — bring back everything hidden by Isolate / Hide "UNISOLATEOBJECTS" => { if self.tabs[i].scene.is_isolation_active() { self.tabs[i].scene.end_isolation(); self.command_line .push_info("Isolation ended — all objects shown."); } else { self.command_line.push_info("No hidden objects."); } } // LAYUNISO — restore all layers that were turned off by LAYISO (turn all on) "LAYUNISO" => { self.push_undo_snapshot(i, "LAYUNISO"); let names: Vec = self.tabs[i] .scene .document .layers .iter() .map(|l| l.name.clone()) .collect(); for name in names { if let Some(dl) = self.tabs[i].scene.document.layers.get_mut(&name) { dl.turn_on(); } } self.tabs[i].scene.bump_geometry(); self.tabs[i].dirty = true; self.refresh_layer_panel(); self.command_line .push_info("LAYUNISO: all layers restored."); } "LAYMATCH" | "LAYMCH" => { use crate::modules::draw::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::draw::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::draw::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::super::helpers::next_group_auto_name(&self.tabs[i].scene); use crate::modules::draw::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::draw::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."); } } } _ => return None, } Some(self.finish_dispatch(cmd)) } }