2026-03-22 22:44:32 +03:00
|
|
|
|
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<Message> {
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2026-03-31 00:31:28 +03:00
|
|
|
|
// 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;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
|
|
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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<String> = 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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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<String> = 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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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<String> = 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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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::<Vec<_>>()
|
|
|
|
|
|
{
|
|
|
|
|
|
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::<Vec<_>>()
|
|
|
|
|
|
{
|
|
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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<String> = 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" => {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::layers::match_layer::LayMatchCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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" => {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::properties::match_prop::MatchPropCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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);
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::groups::group::GroupCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::groups::ungroup::UngroupCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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);
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::clipboard::paste::PasteCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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));
|
2026-04-02 23:05:57 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"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));
|
2026-03-22 22:44:32 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"STRETCH"|"SS" => {
|
|
|
|
|
|
let handles: Vec<_> = self.tabs[i].scene.selected_entities()
|
|
|
|
|
|
.into_iter().map(|(h, _)| h).collect();
|
|
|
|
|
|
if handles.is_empty() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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() {
|
2026-03-22 23:21:29 +03:00
|
|
|
|
use crate::modules::home::select::SelectObjectsCommand;
|
2026-03-22 22:44:32 +03:00
|
|
|
|
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<acadrust::EntityType>)> = 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.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"HELP"|"?" => {
|
|
|
|
|
|
self.command_line.push_output(
|
|
|
|
|
|
"Draw: LINE CIRCLE ARC PLINE POINT ELLIPSE SPLINE | \
|
|
|
|
|
|
Modify: MOVE COPY ROTATE SCALE MIRROR ERASE | \
|
|
|
|
|
|
Text: TEXT MTEXT | File: OPEN SAVE SAVEAS"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
"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" {
|
2026-03-31 08:05:17 +03:00
|
|
|
|
self.command_line.push_error("MVIEW: switch to a paper space layout first.");
|
2026-03-22 22:44:32 +03:00
|
|
|
|
} 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));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 12:52:07 +03:00
|
|
|
|
// ── MSPACE / PSPACE ───────────────────────────────────────────
|
|
|
|
|
|
"MS"|"MSPACE" => {
|
|
|
|
|
|
return Task::done(Message::MspaceCommand);
|
|
|
|
|
|
}
|
|
|
|
|
|
"PSPACE" => {
|
|
|
|
|
|
return Task::done(Message::PspaceCommand);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Feat: viewport display lock, extended properties, VPLAYER and VPORTS commands
- Enforce status.locked in pan_active_viewport and zoom_active_viewport:
locked viewports now reject pan/zoom inputs entirely
- Add perspective, render_mode, shade_plot_mode, hide_plot, ucs_icon_visible
fields to the viewport property panel with appropriate pickers/toggles
- Add VPORTS command: lists all user viewports in the current layout
(id, size, position, scale, on/locked state) in the command line
- Add VPLAYER command: per-viewport layer freeze/thaw from the command line
(F <layer> to freeze, T <layer> to thaw, Enter to exit)
- Add CmdResult::VpLayerUpdate variant and its handler in apply_cmd_result
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 23:52:18 +03:00
|
|
|
|
// ── 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<String> = {
|
|
|
|
|
|
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));
|
|
|
|
|
|
}
|
2026-04-02 19:13:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── 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<acadrust::Handle> = 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)");
|
|
|
|
|
|
}
|
2026-04-02 19:19:12 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 23:16:37 +03:00
|
|
|
|
// ── 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<String> = 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 <name>");
|
|
|
|
|
|
} 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 <name>");
|
|
|
|
|
|
} 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 <name> — 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 <name> | UCS DELETE <name> | UCS W"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 19:19:12 +03:00
|
|
|
|
// ── 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<String> = 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 <name>");
|
|
|
|
|
|
} 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 <name>");
|
|
|
|
|
|
} 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 <name>");
|
|
|
|
|
|
} 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 <name> 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 <name> | VIEW RESTORE <name> | VIEW DELETE <name>"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-04-02 23:14:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── 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<String> = 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 <name> — 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 <name>"));
|
|
|
|
|
|
} 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 <name> <font_file>
|
|
|
|
|
|
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 <style> <font_file>"));
|
|
|
|
|
|
} else if let Some(s) = self.tabs[i].scene.document.text_styles.get_mut(&style_name) {
|
|
|
|
|
|
s.font_file = font.clone();
|
|
|
|
|
|
self.push_undo_snapshot(i, "STYLE FONT");
|
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
|
self.command_line.push_output(&format!("{prefix}: '{style_name}' font set to '{font}'."));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.command_line.push_error(&format!("{prefix}: style '{style_name}' not found."));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
"WIDTH" | "W" => {
|
|
|
|
|
|
// STYLE WIDTH <name> <factor>
|
|
|
|
|
|
let style_name = parts.get(1).map(|s| s.trim()).unwrap_or("").to_string();
|
|
|
|
|
|
let factor_str = parts.get(2).map(|s| s.trim()).unwrap_or("");
|
|
|
|
|
|
if let Ok(factor) = factor_str.parse::<f64>() {
|
|
|
|
|
|
if let Some(s) = self.tabs[i].scene.document.text_styles.get_mut(&style_name) {
|
|
|
|
|
|
s.width_factor = factor;
|
|
|
|
|
|
self.push_undo_snapshot(i, "STYLE WIDTH");
|
|
|
|
|
|
self.tabs[i].dirty = true;
|
|
|
|
|
|
self.command_line.push_output(&format!("{prefix}: '{style_name}' width factor set to {factor:.3}."));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.command_line.push_error(&format!("{prefix}: style '{style_name}' not found."));
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
self.command_line.push_error(&format!("Usage: {prefix} WIDTH <style> <factor>"));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {
|
|
|
|
|
|
self.command_line.push_info(&format!(
|
|
|
|
|
|
"Usage: {prefix} LIST | NEW <name> | FONT <style> <file> | WIDTH <style> <factor>"
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
Feat: viewport display lock, extended properties, VPLAYER and VPORTS commands
- Enforce status.locked in pan_active_viewport and zoom_active_viewport:
locked viewports now reject pan/zoom inputs entirely
- Add perspective, render_mode, shade_plot_mode, hide_plot, ucs_icon_visible
fields to the viewport property panel with appropriate pickers/toggles
- Add VPORTS command: lists all user viewports in the current layout
(id, size, position, scale, on/locked state) in the command line
- Add VPLAYER command: per-viewport layer freeze/thaw from the command line
(F <layer> to freeze, T <layer> to thaw, Enter to exit)
- Add CmdResult::VpLayerUpdate variant and its handler in apply_cmd_result
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 23:52:18 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 08:05:17 +03:00
|
|
|
|
// ── Plot / Page Setup ──────────────────────────────────────────
|
|
|
|
|
|
"PRINT"|"PLOT"|"EXPORT" => {
|
|
|
|
|
|
return Task::done(Message::PlotExport);
|
|
|
|
|
|
}
|
2026-03-31 12:52:07 +03:00
|
|
|
|
"PAGESETUP" => {
|
2026-03-31 08:05:17 +03:00
|
|
|
|
if self.tabs[i].scene.current_layout == "Model" {
|
|
|
|
|
|
self.command_line.push_error("PAGESETUP: switch to a paper space layout first.");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return Task::done(Message::PageSetupOpen);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-22 22:44:32 +03:00
|
|
|
|
_ => self.command_line.push_error(&format!("Unknown command: {cmd}")),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Focus the command line whenever a command just became active.
|
|
|
|
|
|
let i = self.active_tab;
|
|
|
|
|
|
if self.tabs[i].active_cmd.is_some() {
|
|
|
|
|
|
self.tabs[i].last_cmd = Some(cmd.to_string());
|
|
|
|
|
|
self.focus_cmd_input()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Task::none()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|