feat(units): read back every form the drawing writes

The readout learned to write lengths as 5'-6 1/2" and angles as
N 45d0'0" E, but typed input was still a bare parse::<f64>() — a
coordinate could be displayed and not entered. Give the formatters
their inverses.

parse_length takes feet and inches separated by a dash, a space, or
nothing, with the closing quote optional, and whole-plus-fraction in
any of those forms: 5', 60", 5'-9", 5'9-1/2, 9-1/2, 1/2. A leading
minus is a sign; the one inside 5'-9" is a separator, since after feet
there is nothing left to subtract from.

parse_angle reads the mark that says which convention an angle is in —
g for grads, r for radians, d/'/" for degrees-minutes-seconds, compass
letters for a surveyor's bearing — and reads a bare number in whichever
convention the drawing is set to.

ANGBASE and ANGDIR now apply, but only where they mean something. Which
way a thing points is measured from the drawing's zero and runs the way
it says; how wide an arc opens is the same number whatever zero is
counted from. format_direction and parse_direction are the pair for
directions, and the polar readout and polar input both go through them,
so the bearing shown is the bearing accepted. format_angle stays for
sizes, where applying a base angle would corrupt the value.

The command prompts follow the same rule, each classified rather than
swept: OFFSET distance, FILLET radius, CHAMFER distances, ARRAY and
MINSERT spacing, TRACE width, HELIX, and EXTRUDE height read lengths;
the four dimension text-rotation prompts, ARRAY's total angle and
INSERT's rotation read angles. MLINE's scale and INSERT's scale factor
are left alone — a multiplier has no unit to be written in. INSERT read
both through one parse; they are now separate.

The typed variants also take a decimal comma, which a single-value
prompt has no other use for. parse_length itself does not: inside a
coordinate the comma separates the axes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5 committed 2026-08-07 23:46:49 +03:00
1 parent 523b85f42b
commit 6cbec232bb
18 files changed
+298 -58

No files matched your search

+8 -3
View File
@@ -177,7 +177,8 @@ impl CadCommand for ArrayRectCommand {
let row_sp = if t.is_empty() {
self.default_row_sp
} else {
let v = t.parse::<f64>().unwrap_or(self.default_row_sp);
let v = crate::entities::common::parse_typed_length(t)
.unwrap_or(self.default_row_sp);
defaults::set_array_row_sp(v);
self.default_row_sp = v;
v
@@ -189,7 +190,8 @@ impl CadCommand for ArrayRectCommand {
let col_sp = if t.is_empty() {
self.default_col_sp
} else {
let v = t.parse::<f64>().unwrap_or(self.default_col_sp);
let v = crate::entities::common::parse_typed_length(t)
.unwrap_or(self.default_col_sp);
defaults::set_array_col_sp(v);
v
};
@@ -341,7 +343,10 @@ impl CadCommand for ArrayPolarCommand {
let total_deg = if t.is_empty() {
self.default_angle
} else {
let v = t.parse::<f64>().unwrap_or(self.default_angle);
// Held in degrees, which is what the stored default is.
let v = crate::entities::common::parse_typed_angle(t)
.map(f64::to_degrees)
.unwrap_or(self.default_angle);
defaults::set_array_p_angle(v);
v
};
+5 -5
View File
@@ -1271,7 +1271,7 @@ impl CadCommand for FilletCommand {
self.resume_after_radius();
return Some(CmdResult::NeedPoint);
}
if let Ok(v) = t.replace(',', ".").parse::<f64>() {
if let Some(v) = crate::entities::common::parse_typed_length(t) {
if v >= 0.0 {
self.radius = v;
defaults::set_fillet_radius(v);
@@ -1293,7 +1293,7 @@ impl CadCommand for FilletCommand {
// "R 5.0" inline shorthand
if upper.starts_with('R') {
let body = t[1..].trim();
if let Ok(v) = body.replace(',', ".").parse::<f64>() {
if let Some(v) = crate::entities::common::parse_typed_length(body) {
if v >= 0.0 {
self.radius = v;
defaults::set_fillet_radius(v);
@@ -1687,7 +1687,7 @@ impl CadCommand for ChamferCommand {
self.step = ChamferStep::WaitingForDist2;
return Some(CmdResult::NeedPoint);
}
if let Ok(v) = t.replace(',', ".").parse::<f64>() {
if let Some(v) = crate::entities::common::parse_typed_length(t) {
self.dist1 = v.max(0.0);
defaults::set_chamfer_dist1(self.dist1);
self.step = ChamferStep::WaitingForDist2;
@@ -1703,7 +1703,7 @@ impl CadCommand for ChamferCommand {
self.resume_after_dist();
return Some(CmdResult::NeedPoint);
}
if let Ok(v) = t.replace(',', ".").parse::<f64>() {
if let Some(v) = crate::entities::common::parse_typed_length(t) {
self.dist2 = v.max(0.0);
defaults::set_chamfer_dist2(self.dist2);
self.resume_after_dist();
@@ -1727,7 +1727,7 @@ impl CadCommand for ChamferCommand {
let body = t[1..].trim();
let parts: Vec<f64> = body
.split_whitespace()
.filter_map(|s| s.replace(',', ".").parse::<f64>().ok())
.filter_map(crate::entities::common::parse_typed_length)
.collect();
if !parts.is_empty() {
if let Some(&v) = parts.first() {
+2 -2
View File
@@ -1004,7 +1004,7 @@ impl CadCommand for OffsetCommand {
if t.eq_ignore_ascii_case("t") || t.eq_ignore_ascii_case("through") {
return Some(self.advance_from_distance(None));
}
if let Ok(d) = t.parse::<f64>() {
if let Some(d) = crate::entities::common::parse_typed_length(&t) {
let d = d.abs().max(1e-9);
defaults::set_offset_dist(d);
return Some(self.advance_from_distance(Some(d)));
@@ -1021,7 +1021,7 @@ impl CadCommand for OffsetCommand {
return Some(CmdResult::NeedPoint);
}
if !t.is_empty() {
if let Ok(d) = t.parse::<f64>() {
if let Some(d) = crate::entities::common::parse_typed_length(&t) {
let d = d.abs().max(1e-9);
defaults::set_offset_dist(d);
*locked = Some(d);