From c7a7f2f7420349ac8b1de13f98fd6b20d2a7b9e4 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 4 Jun 2026 10:39:24 +0300 Subject: [PATCH] fix(commands): finish the world-XY coordinate migration (#54) The drawing plane is world XY, but break, join, rectangular array, the stretch/window move, the spline preview wire and several doc comments still used the stale "Y-up XZ" mapping (world Z = DXF Y). That made those commands operate against a constant-elevation axis: arc/circle/ line/spline break and join picked the wrong points, rectangular array stepped rows along the Z (elevation) axis, window-stretch tested the wrong window bound, and the spline wire drew in the wrong plane. Map every drawing-plane coordinate through .y (elevation on .z), matching the entity builders, the inquiry commands and the central transform. Stale Y-up comments updated to avoid future confusion. Co-Authored-By: Claude Opus 4.8 --- src/app/cmd_result.rs | 10 +++++----- src/app/mod.rs | 2 +- src/command/mod.rs | 8 ++++---- src/modules/home/modify/align.rs | 2 +- src/modules/home/modify/array.rs | 5 +++-- src/modules/home/modify/break_cmd.rs | 28 +++++++++++++-------------- src/modules/home/modify/join.rs | 6 +++--- src/modules/home/modify/lengthen.rs | 4 ++-- src/modules/home/modify/spline_ops.rs | 4 ++-- 9 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/app/cmd_result.rs b/src/app/cmd_result.rs index f00689321..f75a76436 100644 --- a/src/app/cmd_result.rs +++ b/src/app/cmd_result.rs @@ -723,7 +723,7 @@ impl OpenCADStudio { }, ); } - // Step 3: rotate in XZ plane by angle_rad + // Step 3: rotate in the XY plane by angle_rad if angle_rad.abs() > 1e-4 { self.tabs[i].scene.transform_entities( &handles, @@ -989,16 +989,16 @@ impl OpenCADStudio { let mut count = 0usize; // Helper: is DXF point (x, y) inside the world-space window? - // World XZ = DXF XY. + // Drawing plane is world XY (= DXF XY). let in_win = |x: f64, y: f64| -> bool { let wx = x as f32; let wy = y as f32; - wx >= win_min.x && wx <= win_max.x && wy >= win_min.z && wy <= win_max.z + wx >= win_min.x && wx <= win_max.x && wy >= win_min.y && wy <= win_max.y }; let dx = delta.x as f64; - let dy = delta.z as f64; // world Z = DXF Y - let dz = delta.y as f64; + let dy = delta.y as f64; // drawing plane is world XY + let dz = delta.z as f64; for handle in &handles { let Some(entity) = self.tabs[i].scene.document.get_entity_mut(*handle) else { diff --git a/src/app/mod.rs b/src/app/mod.rs index 58502e920..11d80c305 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -252,7 +252,7 @@ pub(super) struct OpenCADStudio { update_notice_body: Option, /// In-memory clipboard: cloned entities waiting to be pasted. clipboard: Vec, - /// Centroid of the clipboard entities (XZ plane, Y-up). + /// Centroid of the clipboard entities (world XY plane). clipboard_centroid: glam::Vec3, /// True while the Shift key is held — drives subtractive pick (Shift+click /// removes the picked entity from the selection). Tracked from keyboard diff --git a/src/command/mod.rs b/src/command/mod.rs index 412a84c73..e73bedf00 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -15,13 +15,13 @@ use glam::Vec3; /// A geometric transformation applied to existing entities. #[derive(Clone)] pub enum EntityTransform { - /// Move every point by the given world-space delta (Y-up XZ plane). + /// Move every point by the given world-space delta (world XY plane). Translate(Vec3), - /// Rotate around `center` by `angle_rad` in the XZ plane (Y-up). + /// Rotate around `center` by `angle_rad` in the world XY plane. Rotate { center: Vec3, angle_rad: f32 }, /// Uniform scale from `center` by `factor`. Scale { center: Vec3, factor: f32 }, - /// Mirror across the line through `p1`→`p2` in the XZ plane (Y-up). + /// Mirror across the line through `p1`→`p2` in the world XY plane. Mirror { p1: Vec3, p2: Vec3 }, } @@ -32,7 +32,7 @@ pub enum EntityTransform { pub enum TangentObject { /// Infinite line through two world-space XZ-plane points. Line { p1: Vec3, p2: Vec3 }, - /// Circle in the XZ plane. + /// Circle in the world XY plane. Circle { center: Vec3, radius: f32 }, } diff --git a/src/modules/home/modify/align.rs b/src/modules/home/modify/align.rs index 5b76bb756..fea32cf7e 100644 --- a/src/modules/home/modify/align.rs +++ b/src/modules/home/modify/align.rs @@ -151,7 +151,7 @@ impl AlignCommand { _ => return CmdResult::Cancel, }; - // Build transform: move s1→d1, rotate so s2-s1 aligns with d2-d1 (in XZ plane) + // Build transform: move s1→d1, rotate so s2-s1 aligns with d2-d1 (in the world XY plane) let src_vec = s2 - s1; let dst_vec = d2 - d1; diff --git a/src/modules/home/modify/array.rs b/src/modules/home/modify/array.rs index a20f3f0ed..a76459232 100644 --- a/src/modules/home/modify/array.rs +++ b/src/modules/home/modify/array.rs @@ -760,11 +760,12 @@ impl Array3DCommand { if l == 0 && r == 0 && c == 0 { continue; } - // World: X=col dir, Z=row dir (DXF Y), Y=level (up) + // Drawing plane is world XY: X = col dir, Y = row dir, + // Z = level (elevation). t.push(EntityTransform::Translate(Vec3::new( col_sp * c as f32, - lvl_sp * l as f32, row_sp * r as f32, + lvl_sp * l as f32, ))); } } diff --git a/src/modules/home/modify/break_cmd.rs b/src/modules/home/modify/break_cmd.rs index 6e11fb13a..4df6948a6 100644 --- a/src/modules/home/modify/break_cmd.rs +++ b/src/modules/home/modify/break_cmd.rs @@ -53,10 +53,10 @@ pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option Vec { let s = Vec3::new( line.start.x as f32, - line.start.z as f32, line.start.y as f32, + line.start.z as f32, ); - let e = Vec3::new(line.end.x as f32, line.end.z as f32, line.end.y as f32); + let e = Vec3::new(line.end.x as f32, line.end.y as f32, line.end.z as f32); let dir = e - s; let len2 = dir.length_squared(); if len2 < 1e-12 { @@ -96,10 +96,10 @@ fn break_line(line: &LineEnt, p1: Vec3, p2: Vec3) -> Vec { fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec { let cx = arc.center.x as f32; - let cy = arc.center.z as f32; // Y-up: DXF Y→world Z + let cy = arc.center.y as f32; let r = arc.radius as f32; - // Project p1 and p2 onto the arc (use XZ plane) + // Project p1 and p2 onto the arc (world XY plane) let a1 = angle_on_arc(cx, cy, p1); let a2 = angle_on_arc(cx, cy, p2); @@ -127,7 +127,7 @@ fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec { fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec { let cx = circle.center.x as f32; - let cy = circle.center.z as f32; + let cy = circle.center.y as f32; let a1 = angle_on_arc(cx, cy, p1); let a2 = angle_on_arc(cx, cy, p2); @@ -193,7 +193,7 @@ fn break_lwpolyline(p: &LwPolyline, p1: Vec3, p2: Vec3) -> Vec { fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { // Compute the eccentric-anomaly parameter of a world point relative to ellipse. - // World coords use XZ plane (Y-up), so DXF X→world X, DXF Y→world Z. + // World = DXF (XY drawing plane). let cx = ell.center.x; let cy = ell.center.y; let a = (ell.major_axis.x.powi(2) + ell.major_axis.y.powi(2)).sqrt(); @@ -207,7 +207,7 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { // Project a point onto the ellipse parameter (eccentric anomaly) let param_of = |pt: Vec3| -> f64 { let rx = pt.x as f64 - cx; - let ry = pt.z as f64 - cy; // Y-up: DXF Y → world Z + let ry = pt.y as f64 - cy; let xl = rx * nx + ry * ny; let yl = -rx * ny + ry * nx; yl.atan2(xl) // atan2(yl/b*b, xl/a*a) simplifies to atan2(yl,xl) for ordering @@ -256,10 +256,10 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { // ── Small utilities ──────────────────────────────────────────────────────── -/// Returns the angle (radians, 0-2π) of `pt` viewed from (cx, cy) in the XZ plane. +/// Returns the angle (radians, 0-2π) of `pt` viewed from (cx, cy) in the world XY plane. fn angle_on_arc(cx: f32, cy: f32, pt: Vec3) -> f32 { let dx = pt.x - cx; - let dy = pt.z - cy; // XZ plane + let dy = pt.y - cy; dy.atan2(dx).rem_euclid(std::f32::consts::TAU) } @@ -283,7 +283,7 @@ fn nearest_pline_param(p: &LwPolyline, pt: Vec3) -> usize { .enumerate() .min_by_key(|(_, v)| { let dx = v.location.x as f32 - pt.x; - let dy = v.location.y as f32 - pt.z; + let dy = v.location.y as f32 - pt.y; ((dx * dx + dy * dy) * 1e6) as i64 }) .map(|(i, _)| i) @@ -291,8 +291,8 @@ fn nearest_pline_param(p: &LwPolyline, pt: Vec3) -> usize { } fn world_to_dxf(v: Vec3) -> Vec3 { - // world Y-up: X→X, Z→DXF Y, Y→DXF Z - Vec3::new(v.x, v.z, v.y) + // World = DXF (identity). + Vec3::new(v.x, v.y, v.z) } fn vec3_to_v3(v: Vec3) -> Vector3 { @@ -301,11 +301,11 @@ fn vec3_to_v3(v: Vec3) -> Vector3 { fn break_spline(spl: &SplineEnt, p1: Vec3, p2: Vec3) -> Vec { // Find the two nearest parameters to p1 and p2 (DXF XY: world x, z). - let t1 = match spline_nearest_t(spl, p1.x as f64, p1.z as f64) { + let t1 = match spline_nearest_t(spl, p1.x as f64, p1.y as f64) { Some(t) => t, None => return vec![EntityType::Spline(spl.clone())], }; - let t2 = match spline_nearest_t(spl, p2.x as f64, p2.z as f64) { + let t2 = match spline_nearest_t(spl, p2.x as f64, p2.y as f64) { Some(t) => t, None => return vec![EntityType::Spline(spl.clone())], }; diff --git a/src/modules/home/modify/join.rs b/src/modules/home/modify/join.rs index ae879570b..f546969bd 100644 --- a/src/modules/home/modify/join.rs +++ b/src/modules/home/modify/join.rs @@ -92,13 +92,13 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec, } fn try_join_lines(lines: &[&(Handle, &EntityType)]) -> Option<(Vec, Vec)> { - // Collect endpoints in XZ plane + // Collect endpoints in the world XY plane let segs: Vec<_> = lines .iter() .map(|(h, e)| { if let EntityType::Line(l) = e { - let s = Vec3::new(l.start.x as f32, l.start.z as f32, 0.0); - let e2 = Vec3::new(l.end.x as f32, l.end.z as f32, 0.0); + let s = Vec3::new(l.start.x as f32, l.start.y as f32, 0.0); + let e2 = Vec3::new(l.end.x as f32, l.end.y as f32, 0.0); (*h, l, s, e2) } else { unreachable!() diff --git a/src/modules/home/modify/lengthen.rs b/src/modules/home/modify/lengthen.rs index 5823b6a15..8beb1ae91 100644 --- a/src/modules/home/modify/lengthen.rs +++ b/src/modules/home/modify/lengthen.rs @@ -290,7 +290,7 @@ fn lengthen_ellipse(ell: &EllipseEnt, pick_pt: Vec3, mode: &LenMode) -> Option Option f64 { // ── Wire preview ─────────────────────────────────────────────────────────── /// World-space wire points for a Spline (samples 64 segments). -/// Y-up convention: world (x, 0, y) for DXF (x, y). +/// Drawing plane is world XY: DXF (x, y) → world (x, y), z = elevation. pub fn spline_pts_wire(spl: &Spline) -> Vec<[f32; 3]> { let (_, pts) = spline_sample_xy(spl, 64); if pts.is_empty() { @@ -144,6 +144,6 @@ pub fn spline_pts_wire(spl: &Spline) -> Vec<[f32; 3]> { .map(|v| v.z as f32) .unwrap_or(0.0); pts.iter() - .map(|p| [p[0] as f32, elev, p[1] as f32]) + .map(|p| [p[0] as f32, p[1] as f32, elev]) .collect() }