Dimension Space asks for the base, the dimensions and the spacing instead of failing (#483) (#577)

Squash of PR #577 by @MarekSpodBiedry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MarekSpodBiedryandClaude Opus 5.5 authored and GitHub committed 2026-10-11 13:40:39 -04:00
1 parent 3363d4a78c
commit d3ae8f3a8b
2 files changed
+107 -3

No files matched your search

+87 -2
View File
@@ -8,7 +8,7 @@ use cadcraft_geom::{Segment, Vec2, Vec3};
use serde_json::{Value, json};
use super::helpers::v3;
use super::machines::SelectRun;
use super::machines::{SelOutcome, SelectPhase, SelectRun};
use super::*;
use crate::{Accept, EngineError, Input, Interactive, LeaderOrder, MLeaderOptions, Prompt, Result, Session, Step};
@@ -106,7 +106,8 @@ pub fn specs() -> Vec<CommandSpec> {
.interactive(|_| Ok(Box::new(tedit::TEditM::default()))),
CommandSpec::new("dimspace", "Dimension Space", run_dimspace)
.menu(&["Dimension", "Dimension Space"])
.params("{base: hex, handles: [hex], spacing?: number (default DIMDLI)}"),
.params("{base: hex, handles: [hex], spacing?: number (default DIMDLI)}")
.interactive(|_| Ok(Box::new(DimSpaceM::default()))),
]
.into_iter()
.chain(dimedit::specs())
@@ -719,6 +720,90 @@ fn run_dimspace(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "spaced": others.len() }))
}
/// DIMSPACE at the command line: select the base dimension, select the dimensions to space, then
/// the spacing (Enter or Auto takes the DIMDLI-based default).
#[derive(Default)]
struct DimSpaceM {
base: Option<Handle>,
sel: SelectPhase,
spacing_phase: bool,
}
impl DimSpaceM {
fn apply(&self, s: &mut Session, base: Handle, spacing: Option<f64>) -> Result<Step> {
let mut p = serde_json::Map::new();
p.insert("base".into(), json!(base.hex()));
p.insert("handles".into(), json!(self.sel.picked.iter().map(|h| h.hex()).collect::<Vec<_>>()));
if let Some(v) = spacing {
p.insert("spacing".into(), json!(v));
}
let r = run_dimspace(s, &Value::Object(p))?;
s.set_selection(Vec::new());
s.echo(format!("{} dimension(s) spaced.", r.get("spaced").and_then(Value::as_u64).unwrap_or(0)));
Ok(Step::Done)
}
}
impl Interactive for DimSpaceM {
fn name(&self) -> &'static str {
"DIMSPACE"
}
fn begin(&mut self, _s: &mut Session) -> Result<Step> {
self.sel = SelectPhase::single();
Ok(Step::Continue)
}
fn prompt(&self, _s: &Session) -> Prompt {
if self.spacing_phase {
return Prompt::new("Enter value", Accept::NUMBER).kw(&["Auto"]).default("Auto");
}
if self.base.is_none() {
return Prompt::new("Select base dimension", Accept::SELECT);
}
Prompt::new("Select dimensions to space", Accept::SELECT)
}
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
if self.spacing_phase {
let Some(base) = self.base else { return Ok(Step::Cancel) };
return match i {
Input::Enter => self.apply(s, base, None),
Input::Keyword(k) | Input::Text(k) if k.trim().eq_ignore_ascii_case("a") || k.trim().eq_ignore_ascii_case("auto") => {
self.apply(s, base, None)
}
Input::Text(t) => match super::machines::number(&t).filter(|v| v.is_finite() && *v >= 0.0) {
Some(v) => self.apply(s, base, Some(v)),
None => {
s.echo("Requires a non-negative number or Auto.");
Ok(Step::Continue)
}
},
_ => Ok(Step::Continue),
};
}
match self.sel.feed(s, &i)? {
SelOutcome::More => Ok(Step::Continue),
SelOutcome::Empty => Ok(Step::Done),
SelOutcome::Done(hs) => {
if self.base.is_none() {
match hs.first().copied().filter(|h| !dims_of(s, &[*h]).is_empty()) {
Some(b) => {
self.base = Some(b);
self.sel = SelectPhase::default();
}
None => {
s.echo("Select a dimension.");
self.sel = SelectPhase::single();
}
}
} else {
self.sel.picked = hs;
self.spacing_phase = true;
}
Ok(Step::Continue)
}
}
}
}
/// DIMOVERRIDE at the command line: variable, value (repeat), Enter, then select dimensions.
struct OverrideM {
vars: serde_json::Map<String, Value>,
+20 -1
View File
@@ -2,7 +2,7 @@ use cadcraft_doc::{AssocSnap, DimKind, Dimension, EntityKind, Handle};
use cadcraft_geom::Vec2;
use serde_json::{Value, json};
use crate::Session;
use crate::{Input, Session};
fn h(v: &Value) -> Handle {
Handle::parse_hex(v["handle"].as_str().unwrap()).unwrap()
@@ -299,6 +299,25 @@ fn dimtedit_and_dimspace() {
assert!((dim(&s, b).defpt.y - 1.5).abs() < 1e-9, "{:?}", dim(&s, b).defpt);
}
#[test]
fn dimspace_prompts_for_its_inputs() {
let mut s = Session::new();
let a = h(&s.execute("dimlinear", &json!({ "p1": [0, 0], "p2": [10, 0], "at": [0, 5] })).unwrap());
let b = h(&s.execute("dimlinear", &json!({ "p1": [0, 0], "p2": [20, 0], "at": [0, 9] })).unwrap());
// The menu / typed start used to fail with "`base` dimension is required".
s.start("dimspace").unwrap();
assert!(s.prompt_text().contains("Select base dimension"), "{}", s.prompt_text());
s.input(Input::Pick(vec![a])).unwrap();
assert!(s.prompt_text().contains("Select dimensions to space"), "{}", s.prompt_text());
s.input(Input::Pick(vec![b])).unwrap();
s.input(Input::Enter).unwrap();
assert!(s.prompt_text().contains("Enter value"), "{}", s.prompt_text());
s.input(Input::Text("2".into())).unwrap();
assert!(s.running.is_none());
assert!((dim(&s, a).defpt.y - 5.0).abs() < 1e-9, "{:?}", dim(&s, a).defpt);
assert!((dim(&s, b).defpt.y - 7.0).abs() < 1e-9, "{:?}", dim(&s, b).defpt);
}
#[test]
fn text_style_commands() {
let mut s = Session::new();