forked from 1b-agent/opencadstudio-upstream
feat(commands): step the rest of the keyword/value commands
Convert the remaining bucket-B commands to prompt step by step instead of requiring arguments or printing usage: - KeywordCommand (buttons): VIEW, LINETYPE, SCALELISTEDIT, UCSICON, VSCURRENT. - ValuePromptCommand (one value): LAYDEL (layer), ATTSYNC (block). - TwoValuePromptCommand (new; two values): LAYMRG (source + target), SETVAR (variable + value, blank second reads). Each bare form now starts the stepped front-end and delegates to the existing inline handler; the argument form is kept and is the dispatch target. The direct visual-style shortcuts (HIDDENLINE / XRAY / …) and UCSICON's argument form are untouched. COUNT is intentionally left alone: bare COUNT already reports the tally (a useful inquiry action, not a usage line), so prompting would only add friction.
This commit is contained in:
1 parent
4fa6c49445
commit
4c3509fa51
7 files changed
+186
-16
No files matched your search
@@ -324,7 +324,13 @@ impl OpenCADStudio {
|
||||
// block's current attribute definitions: drop attributes whose tag
|
||||
// no longer exists and add any newly-defined ones (keeping the values
|
||||
// of attributes that remain).
|
||||
cmd if cmd == "ATTSYNC" || cmd.starts_with("ATTSYNC ") => {
|
||||
"ATTSYNC" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new("ATTSYNC", "ATTSYNC block name to sync:");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("ATTSYNC ") => {
|
||||
let arg = cmd.trim_start_matches("ATTSYNC").trim();
|
||||
let blocks = self.tabs[i].scene.custom_block_names();
|
||||
if arg.is_empty() {
|
||||
|
||||
@@ -72,7 +72,22 @@ impl OpenCADStudio {
|
||||
// UCSICON OFF — hide UCS icon in all viewports
|
||||
// UCSICON NOORIGIN — show icon but not at origin (show at corner)
|
||||
// UCSICON ORIGIN — show icon at UCS origin
|
||||
cmd if cmd == "UCSICON" || cmd.starts_with("UCSICON ") => {
|
||||
"UCSICON" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"UCSICON",
|
||||
"UCSICON [On / Off / NoOrigin / Origin]:",
|
||||
vec![
|
||||
("On", "ON", None),
|
||||
("Off", "OFF", None),
|
||||
("NoOrigin", "NOORIGIN", None),
|
||||
("Origin", "ORIGIN", None),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("UCSICON ") => {
|
||||
let sub = cmd.split_whitespace().nth(1).unwrap_or("").to_uppercase();
|
||||
match sub.as_str() {
|
||||
"ON" | "OFF" | "NOORIGIN" | "ORIGIN" => {
|
||||
@@ -746,7 +761,20 @@ impl OpenCADStudio {
|
||||
// SCALELISTEDIT list
|
||||
// SCALELISTEDIT ADD 1:50 add (name is a paper:drawing ratio)
|
||||
// SCALELISTEDIT DELETE 1:50 remove (not the current scale)
|
||||
cmd if cmd == "SCALELISTEDIT" || cmd.starts_with("SCALELISTEDIT ") => {
|
||||
"SCALELISTEDIT" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"SCALELISTEDIT",
|
||||
"SCALELISTEDIT [Add / Delete]:",
|
||||
vec![
|
||||
("Add", "ADD", Some("SCALELISTEDIT ADD new scale ratio (e.g. 1:50):")),
|
||||
("Delete", "DELETE", Some("SCALELISTEDIT DELETE scale ratio to remove:")),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("SCALELISTEDIT ") => {
|
||||
let rest = cmd.trim_start_matches("SCALELISTEDIT").trim();
|
||||
let mut parts = rest.splitn(2, char::is_whitespace);
|
||||
let sub = parts.next().unwrap_or("").to_uppercase();
|
||||
|
||||
@@ -98,10 +98,26 @@ impl OpenCADStudio {
|
||||
// chosen style is reported so the mapping is explicit. (`SOLID` is
|
||||
// intentionally NOT a visual-style verb — it is the 2D filled-polygon
|
||||
// draw command; the shaded ribbon button drives `SetWireframe`.)
|
||||
cmd if cmd == "VS"
|
||||
|| cmd == "VSCURRENT"
|
||||
|| cmd == "SHADEMODE"
|
||||
|| cmd == "HIDDENLINE"
|
||||
"VS" | "VSCURRENT" | "SHADEMODE" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"VSCURRENT",
|
||||
"VSCURRENT visual style [Shaded / Wireframe / Hidden / Realistic / Conceptual / X-ray]:",
|
||||
vec![
|
||||
("Shaded", "SHADED", None),
|
||||
("Wireframe", "WIREFRAME", None),
|
||||
("Hidden", "HIDDEN", None),
|
||||
("Realistic", "REALISTIC", None),
|
||||
("Conceptual", "CONCEPTUAL", None),
|
||||
("X-Ray", "XRAY", None),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
// The named visual-style shortcuts still switch directly, and the
|
||||
// `<name> <style>` argument form (also what the picker dispatches).
|
||||
cmd if cmd == "HIDDENLINE"
|
||||
|| cmd == "XRAY"
|
||||
|| cmd == "REALISTIC"
|
||||
|| cmd == "CONCEPTUAL"
|
||||
|
||||
@@ -400,7 +400,22 @@ impl OpenCADStudio {
|
||||
)));
|
||||
}
|
||||
|
||||
cmd if cmd == "VIEW" || cmd.starts_with("VIEW ") => {
|
||||
"VIEW" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"VIEW",
|
||||
"VIEW [List / Save / Restore / Delete]:",
|
||||
vec![
|
||||
("List", "LIST", None),
|
||||
("Save", "SAVE", Some("VIEW SAVE new view name:")),
|
||||
("Restore", "RESTORE", Some("VIEW RESTORE view name:")),
|
||||
("Delete", "DELETE", Some("VIEW DELETE view name:")),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if 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() {
|
||||
|
||||
@@ -75,7 +75,13 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
// LAYDEL <name> — delete a layer and erase the objects on it.
|
||||
cmd if cmd == "LAYDEL" || cmd.starts_with("LAYDEL ") => {
|
||||
"LAYDEL" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new("LAYDEL", "LAYDEL layer to delete:");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("LAYDEL ") => {
|
||||
let name = cmd.trim_start_matches("LAYDEL").trim();
|
||||
if name.is_empty() {
|
||||
self.command_line.push_info("Usage: LAYDEL <layer name>");
|
||||
@@ -127,7 +133,17 @@ impl OpenCADStudio {
|
||||
|
||||
// LAYMRG <source> <target> — move every object from <source> onto
|
||||
// <target>, then delete the emptied <source> layer.
|
||||
cmd if cmd == "LAYMRG" || cmd.starts_with("LAYMRG ") => {
|
||||
"LAYMRG" => {
|
||||
use crate::command::TwoValuePromptCommand;
|
||||
let c = TwoValuePromptCommand::new(
|
||||
"LAYMRG",
|
||||
"LAYMRG source layer (merged away):",
|
||||
"LAYMRG target layer (kept):",
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("LAYMRG ") => {
|
||||
let rest = cmd.trim_start_matches("LAYMRG").trim();
|
||||
let parts: Vec<&str> = rest.split_whitespace().collect();
|
||||
if parts.len() != 2 {
|
||||
|
||||
@@ -69,11 +69,20 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
// ── LINETYPE management ───────────────────────────────────────
|
||||
cmd if cmd == "LINETYPE"
|
||||
|| cmd == "LT"
|
||||
|| cmd.starts_with("LINETYPE ")
|
||||
|| cmd.starts_with("LT ") =>
|
||||
{
|
||||
"LINETYPE" | "LT" => {
|
||||
use crate::command::KeywordCommand;
|
||||
let c = KeywordCommand::new(
|
||||
"LINETYPE",
|
||||
"LINETYPE [List / Set]:",
|
||||
vec![
|
||||
("List", "LIST", None),
|
||||
("Set", "SET", Some("LINETYPE SET linetype name (ByLayer / ByBlock / …):")),
|
||||
],
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("LINETYPE ") || cmd.starts_with("LT ") => {
|
||||
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();
|
||||
@@ -587,7 +596,17 @@ impl OpenCADStudio {
|
||||
return self.dispatch_styleprops(&format!("SETVAR {cmd}"), i);
|
||||
}
|
||||
|
||||
cmd if cmd == "SETVAR" || cmd.starts_with("SETVAR ") => {
|
||||
"SETVAR" => {
|
||||
use crate::command::TwoValuePromptCommand;
|
||||
let c = TwoValuePromptCommand::new(
|
||||
"SETVAR",
|
||||
"SETVAR variable name:",
|
||||
"SETVAR new value (blank to read):",
|
||||
);
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
}
|
||||
cmd if cmd.starts_with("SETVAR ") => {
|
||||
let rest = cmd.strip_prefix("SETVAR").unwrap_or("").trim();
|
||||
let mut it = rest.splitn(2, char::is_whitespace);
|
||||
let name = it.next().unwrap_or("").to_uppercase();
|
||||
|
||||
@@ -409,6 +409,76 @@ impl CadCommand for KeywordCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic interactive front-end for a two-argument command (LAYMRG source +
|
||||
/// target, SETVAR variable + value…). Prompts for the two values in turn and
|
||||
/// dispatches `<name> <first> <second>` to the existing inline handler. A bare
|
||||
/// Enter on the second value dispatches `<name> <first>` (no second token) so a
|
||||
/// getter-style command (SETVAR reading a variable) still works.
|
||||
pub struct TwoValuePromptCommand {
|
||||
name: &'static str,
|
||||
prompt1: &'static str,
|
||||
prompt2: &'static str,
|
||||
first: Option<String>,
|
||||
}
|
||||
|
||||
impl TwoValuePromptCommand {
|
||||
pub fn new(name: &'static str, prompt1: &'static str, prompt2: &'static str) -> Self {
|
||||
Self {
|
||||
name,
|
||||
prompt1,
|
||||
prompt2,
|
||||
first: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for TwoValuePromptCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
match &self.first {
|
||||
None => self.prompt1.to_string(),
|
||||
Some(_) => self.prompt2.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let t = text.trim();
|
||||
match &self.first {
|
||||
None => {
|
||||
if t.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.first = Some(t.to_string());
|
||||
None
|
||||
}
|
||||
Some(first) => Some(CmdResult::Dispatch(if t.is_empty() {
|
||||
format!("{} {first}", self.name)
|
||||
} else {
|
||||
format!("{} {first} {t}", self.name)
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match &self.first {
|
||||
// Enter on the second step with nothing typed = report / no-op form.
|
||||
Some(first) => CmdResult::Dispatch(format!("{} {first}", self.name)),
|
||||
None => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Result token ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Returned by every `CadCommand` method to tell main.rs what to do.
|
||||
|
||||
Reference in new issue
Block a user