mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 10:56:51 -06:00
Squash of PR #614 by @PedroGuille. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
259 lines
10 KiB
Rust
259 lines
10 KiB
Rust
//! Drafting settings and system variables: ORTHO, OSNAP, GRID, SNAP, POLAR, SETVAR/GETVAR.
|
|
|
|
use serde_json::{Value, json};
|
|
|
|
use super::*;
|
|
use crate::{Accept, Input, Interactive, Prompt, Result, Session, Step};
|
|
|
|
pub(super) mod iso;
|
|
|
|
fn toggle(s: &mut Session, p: &Value, get: fn(&mut crate::Settings) -> &mut bool, name: &str) -> Result<Value> {
|
|
let v = get(&mut s.settings);
|
|
*v = p.get("on").and_then(Value::as_bool).unwrap_or(!*v);
|
|
let on = *v;
|
|
s.touch();
|
|
Ok(json!({ "on": on, "message": format!("<{name} {}>", if on { "on" } else { "off" }) }))
|
|
}
|
|
|
|
pub fn specs() -> Vec<CommandSpec> {
|
|
vec![
|
|
CommandSpec::new("ortho", "Ortho Mode", |s, p| toggle(s, p, |st| &mut st.orthomode, "Ortho"))
|
|
.key("F8")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("grid", "Grid Display", |s, p| toggle(s, p, |st| &mut st.gridmode, "Grid"))
|
|
.key("F7")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("snap", "Snap Mode", |s, p| toggle(s, p, |st| &mut st.snapmode, "Snap"))
|
|
.key("F9")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("polar", "Polar Tracking", |s, p| toggle(s, p, |st| &mut st.polarmode, "Polar"))
|
|
.key("F10")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("otrack", "Object Snap Tracking", |s, p| toggle(s, p, |st| &mut st.otrack, "Object Snap Tracking"))
|
|
.key("F11")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("dynmode", "Dynamic Input", |s, p| toggle(s, p, |st| &mut st.dynmode, "Dynamic Input"))
|
|
.key("F12")
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("lwdisplay", "Show/Hide Lineweight", |s, p| toggle(s, p, |st| &mut st.lwdisplay, "Lineweight"))
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("isodraft", "Isometric Drafting", iso::run_isodraft)
|
|
.params("{on?: bool, plane?: \"left\"|\"top\"|\"right\"|\"orthographic\"} (neither: toggle)")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent()
|
|
.interactive(|_| Ok(Box::new(iso::IsodraftM))),
|
|
CommandSpec::new("isoplane", "Isometric Plane", iso::run_isoplane)
|
|
.key("F5")
|
|
.params("{plane?: \"left\"|\"top\"|\"right\"} (none: the next plane)")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent()
|
|
.interactive(|_| Ok(Box::new(iso::IsoplaneM))),
|
|
CommandSpec::new("transparencydisplay", "Show/Hide Transparency", |s, p| toggle(s, p, |st| &mut st.transparency_display, "Transparency"))
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo(),
|
|
CommandSpec::new("qpmode", "Quick Properties", |s, p| toggle(s, p, |st| &mut st.qpmode, "Quick Properties"))
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("selectioncycling", "Selection Cycling", |s, p| toggle(s, p, |st| &mut st.selection_cycling, "Selection Cycling"))
|
|
.params("{on?: bool}")
|
|
.enabled(always)
|
|
.noundo(),
|
|
CommandSpec::new("osnap", "Object Snap", run_osnap)
|
|
.key("F3")
|
|
.alias(&["os", "ddosnap"])
|
|
.params("{on?: bool, modes?: [\"end\",\"mid\",...] | osmode?: n}")
|
|
.enabled(always)
|
|
.noundo()
|
|
.transparent(),
|
|
CommandSpec::new("dsettings", "Drafting Settings...", run_dsettings)
|
|
.menu(&["Tools", "Drafting Settings..."])
|
|
.alias(&["ds", "se"])
|
|
.params("{snapunit?: [x,y], gridunit?: [x,y], polarang? (degrees), gridmajor?}")
|
|
.enabled(always),
|
|
CommandSpec::new("setvar", "Set Variable", run_setvar)
|
|
.menu(&["Tools", "Inquiry", "Set Variable"])
|
|
.alias(&["set"])
|
|
.params("{name, value}")
|
|
.transparent()
|
|
.interactive(|_| Ok(Box::new(SetvarM::default()))),
|
|
CommandSpec::new("getvar", "Get Variable", run_getvar).params("{name}").enabled(always).noundo(),
|
|
CommandSpec::new("sysvars", "List System Variables", |s, _| Ok(crate::sysvars::list(s))).enabled(always).noundo(),
|
|
]
|
|
}
|
|
|
|
fn run_osnap(s: &mut Session, p: &Value) -> Result<Value> {
|
|
use crate::snap::mode;
|
|
if let Some(n) = p.get("osmode").and_then(Value::as_u64) {
|
|
s.settings.osmode = (n as u32) & 0x7fff;
|
|
}
|
|
if let Some(ms) = p.get("modes").and_then(Value::as_array) {
|
|
let mut bits = 0u32;
|
|
for m in ms.iter().filter_map(Value::as_str) {
|
|
let b = match m.to_ascii_lowercase().get(..3).unwrap_or("") {
|
|
"end" => mode::END,
|
|
"mid" => mode::MID,
|
|
"cen" => mode::CEN,
|
|
"nod" => mode::NOD,
|
|
"qua" => mode::QUA,
|
|
"int" => mode::INT,
|
|
"ins" => mode::INS,
|
|
"per" => mode::PER,
|
|
"tan" => mode::TAN,
|
|
"nea" => mode::NEA,
|
|
"gce" => mode::GCEN,
|
|
"app" => mode::APP,
|
|
"ext" => mode::EXT,
|
|
"par" => mode::PAR,
|
|
_ => 0,
|
|
};
|
|
bits |= b;
|
|
}
|
|
s.settings.osmode = bits;
|
|
}
|
|
if p.get("osmode").is_none() && p.get("modes").is_none() {
|
|
let on = p.get("on").and_then(Value::as_bool).unwrap_or(s.settings.osmode & mode::OFF != 0);
|
|
if on {
|
|
s.settings.osmode &= !mode::OFF;
|
|
} else {
|
|
s.settings.osmode |= mode::OFF;
|
|
}
|
|
}
|
|
s.touch();
|
|
let on = s.settings.osmode & mode::OFF == 0;
|
|
let active: Vec<&str> = mode::ALL.iter().filter(|(b, _)| s.settings.osmode & b != 0).map(|(_, n)| *n).collect();
|
|
Ok(json!({ "on": on, "osmode": s.settings.osmode, "modes": active, "message": format!("<Osnap {}>", if on { "on" } else { "off" }) }))
|
|
}
|
|
|
|
fn run_dsettings(s: &mut Session, p: &Value) -> Result<Value> {
|
|
if let Some(v) = point_param(p, "snapunit").filter(|v| v.x > 0.0 && v.y > 0.0) {
|
|
crate::sysvars::set(s, "SNAPUNIT", &json!([v.x, v.y]))?;
|
|
}
|
|
if let Some(v) = point_param(p, "gridunit").filter(|v| v.x >= 0.0 && v.y >= 0.0) {
|
|
crate::sysvars::set(s, "GRIDUNIT", &json!([v.x, v.y]))?;
|
|
}
|
|
if let Some(a) = p.get("polarang").and_then(Value::as_f64).filter(|a| *a > 0.0) {
|
|
s.settings.polarang = a.to_radians();
|
|
}
|
|
if let Some(m) = p.get("gridmajor").and_then(Value::as_u64) {
|
|
s.settings.gridmajor = (m as u32).clamp(1, 100);
|
|
}
|
|
s.touch();
|
|
Ok(serde_json::to_value(s.drafting_settings()).unwrap_or(Value::Null))
|
|
}
|
|
|
|
fn run_setvar(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let name = str_param(p, "name").ok_or_else(|| bad("setvar", "`name` is required"))?;
|
|
let v = p.get("value").ok_or_else(|| bad("setvar", "`value` is required"))?;
|
|
crate::sysvars::set(s, name, v)?;
|
|
s.touch();
|
|
Ok(json!({ "name": name.to_ascii_uppercase(), "value": crate::sysvars::get(s, name) }))
|
|
}
|
|
|
|
/// A system variable value as the command line shows it.
|
|
fn show_value(v: &Value) -> String {
|
|
match v {
|
|
Value::String(t) => t.clone(),
|
|
Value::Array(a) => a.iter().map(show_value).collect::<Vec<_>>().join(","),
|
|
other => other.to_string(),
|
|
}
|
|
}
|
|
|
|
/// SETVAR at the command line: "Enter variable name or [?]", then "Enter new value for NAME <current>".
|
|
#[derive(Default)]
|
|
struct SetvarM {
|
|
name: Option<String>,
|
|
}
|
|
|
|
impl Interactive for SetvarM {
|
|
fn name(&self) -> &'static str {
|
|
"SETVAR"
|
|
}
|
|
fn prompt(&self, s: &Session) -> Prompt {
|
|
// Not a free-text prompt: Space ends the input, as it does for names and numbers.
|
|
let accept = Accept { number: true, ..Accept::TEXT };
|
|
match &self.name {
|
|
None => Prompt::new("Enter variable name", accept).kw(&["?"]),
|
|
Some(n) => {
|
|
let p = Prompt::new(format!("Enter new value for {n}"), accept);
|
|
match crate::sysvars::get(s, n) {
|
|
Some(v) => p.default(show_value(&v)),
|
|
None => p,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
|
|
match (self.name.clone(), i) {
|
|
(_, Input::Enter | Input::Cancel) => Ok(Step::Done),
|
|
(None, Input::Keyword(_)) => {
|
|
if let Value::Object(m) = crate::sysvars::list(s) {
|
|
for (k, v) in m {
|
|
s.echo(format!("{k:<12} {}", show_value(&v)));
|
|
}
|
|
}
|
|
Ok(Step::Done)
|
|
}
|
|
(None, Input::Text(t)) => {
|
|
let n = t.trim().to_ascii_uppercase();
|
|
match crate::sysvars::get(s, &n) {
|
|
None => {
|
|
s.echo(format!("Unknown variable {n}. Type SETVAR ? to list variables."));
|
|
Ok(Step::Done)
|
|
}
|
|
Some(v) if crate::sysvars::is_read_only(&n) => {
|
|
s.echo(format!("{n} = {} (read only)", show_value(&v)));
|
|
Ok(Step::Done)
|
|
}
|
|
Some(_) => {
|
|
self.name = Some(n);
|
|
Ok(Step::Continue)
|
|
}
|
|
}
|
|
}
|
|
(Some(n), Input::Text(t)) => match crate::sysvars::set(s, &n, &Value::String(t)) {
|
|
Ok(()) => {
|
|
s.touch();
|
|
Ok(Step::Done)
|
|
}
|
|
Err(e) => {
|
|
s.echo(e.to_string());
|
|
Ok(Step::Continue)
|
|
}
|
|
},
|
|
_ => Ok(Step::Continue),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_getvar(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let name = str_param(p, "name").ok_or_else(|| bad("getvar", "`name` is required"))?;
|
|
let v = crate::sysvars::get(s, name).ok_or_else(|| bad("getvar", format!("unknown variable `{name}`")))?;
|
|
Ok(json!({ "name": name.to_ascii_uppercase(), "value": v }))
|
|
}
|