forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
523b85f42b
commit
6cbec232bb
18 files changed
+298
-58
No files matched your search
+18
-13
@@ -20,7 +20,13 @@ pub(crate) enum CoordKind {
|
||||
/// `distance<angle<elevation`.
|
||||
/// A leading `@` marks the value relative to the last point; a leading
|
||||
/// `#` forces absolute. Separators: comma or semicolon.
|
||||
///
|
||||
/// Each number is read by the same pair that writes it, so anything the
|
||||
/// drawing displays can be typed straight back: `5'-6 1/2"`, `9 1/2`,
|
||||
/// `@72'8"<n45d20'6"e`. Angles after `<` are directions, and so are counted
|
||||
/// from the drawing's zero and run the way it says.
|
||||
pub(crate) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
use crate::entities::common::{parse_direction, parse_length};
|
||||
let trimmed = text.trim();
|
||||
let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') {
|
||||
(CoordKind::Relative, r)
|
||||
@@ -29,12 +35,13 @@ pub(crate) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
} else {
|
||||
(CoordKind::Default, trimmed)
|
||||
};
|
||||
let number = |value: &str| value.trim().parse::<f64>().ok();
|
||||
if let Some((distance, angles)) = rest.split_once('<') {
|
||||
let distance = number(distance)?;
|
||||
let distance = parse_length(distance)?;
|
||||
if let Some((azimuth, elevation)) = angles.split_once('<') {
|
||||
let azimuth = number(azimuth)?.to_radians();
|
||||
let elevation = number(elevation)?.to_radians();
|
||||
let azimuth = parse_direction(azimuth)?;
|
||||
// The rise out of the plane is a size, not a compass direction, so
|
||||
// it is not counted from the drawing's zero.
|
||||
let elevation = crate::entities::common::parse_angle(elevation)?;
|
||||
let horizontal = distance * elevation.cos();
|
||||
return Some((
|
||||
glam::DVec3::new(
|
||||
@@ -45,13 +52,12 @@ pub(crate) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
kind,
|
||||
));
|
||||
}
|
||||
let angle_and_z: Vec<&str> = angles.split(|c| c == ',' || c == ';').collect();
|
||||
let (angle, z) = match angle_and_z.as_slice() {
|
||||
[angle] => (number(angle)?, 0.0),
|
||||
[angle, z] => (number(angle)?, number(z)?),
|
||||
_ => return None,
|
||||
// A cylindrical `distance<angle,z` splits on the comma — but a
|
||||
// surveyor's bearing has none, so nothing here can eat one.
|
||||
let (angle, z) = match angles.split_once(|c| c == ',' || c == ';') {
|
||||
Some((angle, z)) => (parse_direction(angle)?, parse_length(z)?),
|
||||
None => (parse_direction(angles)?, 0.0),
|
||||
};
|
||||
let angle = angle.to_radians();
|
||||
return Some((
|
||||
glam::DVec3::new(distance * angle.cos(), distance * angle.sin(), z),
|
||||
kind,
|
||||
@@ -59,9 +65,8 @@ pub(crate) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
}
|
||||
let parts: Vec<f64> = rest
|
||||
.split(|c| c == ',' || c == ';')
|
||||
.map(|s| s.trim())
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
.map(parse_length)
|
||||
.collect::<Option<Vec<f64>>>()?;
|
||||
match parts.as_slice() {
|
||||
[x, y] => Some((glam::DVec3::new(*x, *y, 0.0), kind)),
|
||||
[x, y, z] => Some((glam::DVec3::new(*x, *y, *z), kind)),
|
||||
|
||||
@@ -89,12 +89,9 @@ impl OpenCADStudio {
|
||||
// lengths/angles per LUNITS / LUPREC / AUNITS / AUPREC.
|
||||
{
|
||||
let h = &self.tabs[i].scene.document.header;
|
||||
crate::entities::common::set_unit_context(crate::entities::common::UnitContext {
|
||||
lunits: h.linear_unit_format,
|
||||
luprec: h.linear_unit_precision,
|
||||
aunits: h.angular_unit_format,
|
||||
auprec: h.angular_unit_precision,
|
||||
});
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext::from_header(h),
|
||||
);
|
||||
}
|
||||
|
||||
let layer_names: Vec<String> = self.tabs[i]
|
||||
|
||||
+3
-6
@@ -1664,12 +1664,9 @@ impl OpenCADStudio {
|
||||
// every refresh; the status bar redraws on its own schedule
|
||||
// and cannot rely on that having happened.
|
||||
crate::entities::common::set_unit_context(
|
||||
crate::entities::common::UnitContext {
|
||||
lunits: tab.scene.document.header.linear_unit_format,
|
||||
luprec: tab.scene.document.header.linear_unit_precision,
|
||||
aunits: tab.scene.document.header.angular_unit_format,
|
||||
auprec: tab.scene.document.header.angular_unit_precision,
|
||||
},
|
||||
crate::entities::common::UnitContext::from_header(
|
||||
&tab.scene.document.header,
|
||||
),
|
||||
);
|
||||
let status_menu_data = crate::ui::statusbar::StatusMenuData {
|
||||
layout_names: layout_names.clone(),
|
||||
|
||||
Reference in new issue
Block a user