forked from 1b-agent/opencadstudio-upstream
feat(ucs): integrate coordinate systems
Route coordinate input, geometry creation, transforms, properties,\nviews, and viewport persistence through a shared working plane.\n\nRefs #484
This commit is contained in:
1 parent
b1d55c6a7e
commit
657d023fb7
61 files changed
+2218
-747
No files matched your search
+55
-5
@@ -5,7 +5,7 @@ use acadrust::tables::Ucs;
|
||||
/// How a typed coordinate should be interpreted relative to the last
|
||||
/// input point.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(super) enum CoordKind {
|
||||
pub(crate) enum CoordKind {
|
||||
/// `@x,y` prefix — offset from the last input point.
|
||||
Relative,
|
||||
/// `#x,y` prefix — world/UCS absolute, overriding DYN.
|
||||
@@ -14,12 +14,13 @@ pub(super) enum CoordKind {
|
||||
Default,
|
||||
}
|
||||
|
||||
/// Parse a typed coordinate string into a Vec3 plus its interpretation.
|
||||
/// Accepts "x,y" → Vec3(x, y, 0)
|
||||
/// "x,y,z" → Vec3(x, y, z)
|
||||
/// Parse a typed coordinate string into a local Vec3 plus its interpretation.
|
||||
/// Accepts Cartesian, polar, cylindrical and spherical forms:
|
||||
/// `x,y`, `x,y,z`, `distance<angle`, `distance<angle,z`, and
|
||||
/// `distance<angle<elevation`.
|
||||
/// A leading `@` marks the value relative to the last point; a leading
|
||||
/// `#` forces absolute. Separators: comma or semicolon.
|
||||
pub(super) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
pub(crate) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
let trimmed = text.trim();
|
||||
let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') {
|
||||
(CoordKind::Relative, r)
|
||||
@@ -28,6 +29,34 @@ pub(super) 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)?;
|
||||
if let Some((azimuth, elevation)) = angles.split_once('<') {
|
||||
let azimuth = number(azimuth)?.to_radians();
|
||||
let elevation = number(elevation)?.to_radians();
|
||||
let horizontal = distance * elevation.cos();
|
||||
return Some((
|
||||
glam::DVec3::new(
|
||||
horizontal * azimuth.cos(),
|
||||
horizontal * azimuth.sin(),
|
||||
distance * elevation.sin(),
|
||||
),
|
||||
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,
|
||||
};
|
||||
let angle = angle.to_radians();
|
||||
return Some((
|
||||
glam::DVec3::new(distance * angle.cos(), distance * angle.sin(), z),
|
||||
kind,
|
||||
));
|
||||
}
|
||||
let parts: Vec<f64> = rest
|
||||
.split(|c| c == ',' || c == ';')
|
||||
.map(|s| s.trim())
|
||||
@@ -40,6 +69,23 @@ pub(super) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod coordinate_parsing_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parses_all_coordinate_forms() {
|
||||
let close = |a: glam::DVec3, b: glam::DVec3| (a - b).length() < 1e-9;
|
||||
assert!(close(parse_coord("1,2").unwrap().0, glam::dvec3(1.0, 2.0, 0.0)));
|
||||
assert!(close(parse_coord("1,2,3").unwrap().0, glam::dvec3(1.0, 2.0, 3.0)));
|
||||
assert!(close(parse_coord("10<90").unwrap().0, glam::dvec3(0.0, 10.0, 0.0)));
|
||||
assert!(close(parse_coord("10<90,4").unwrap().0, glam::dvec3(0.0, 10.0, 4.0)));
|
||||
assert!(close(parse_coord("10<0<30").unwrap().0, glam::dvec3(5.0 * 3.0_f64.sqrt(), 0.0, 5.0)));
|
||||
assert_eq!(parse_coord("@10<0").unwrap().1, CoordKind::Relative);
|
||||
assert_eq!(parse_coord("#1,2").unwrap().1, CoordKind::Absolute);
|
||||
}
|
||||
}
|
||||
|
||||
// ── UCS ↔ WCS converter ─────────────────────────────────────────────────────
|
||||
|
||||
/// The single bridge between WCS (how geometry and the file are stored) and the
|
||||
@@ -122,6 +168,10 @@ impl UcsXform {
|
||||
(self.origin, self.x, self.y, self.z)
|
||||
}
|
||||
|
||||
pub(super) fn working_plane(&self) -> crate::command::WorkingPlane {
|
||||
crate::command::WorkingPlane::new(self.origin, self.x, self.y)
|
||||
}
|
||||
|
||||
/// UCS→world rotation matrix (columns = UCS axes). For consumers that take
|
||||
/// a `Mat4` rotation directly (ViewCube, OTRACK ray directions). The GPU /
|
||||
/// screen layer is f32, so the axes downcast here at the boundary.
|
||||
|
||||
Reference in new issue
Block a user