forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
c04bdcf846
commit
c7a7f2f742
9 files changed
+35
-34
No files matched your search
@@ -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 {
|
||||
|
||||
+1
-1
@@ -252,7 +252,7 @@ pub(super) struct OpenCADStudio {
|
||||
update_notice_body: Option<String>,
|
||||
/// In-memory clipboard: cloned entities waiting to be pasted.
|
||||
clipboard: Vec<acadrust::EntityType>,
|
||||
/// 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
|
||||
|
||||
+4
-4
@@ -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 },
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option<Vec<Entit
|
||||
fn break_line(line: &LineEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
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<EntityType> {
|
||||
|
||||
fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
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<EntityType> {
|
||||
|
||||
fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
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<EntityType> {
|
||||
|
||||
fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec<EntityType> {
|
||||
// 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<EntityType> {
|
||||
// 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<EntityType> {
|
||||
|
||||
// ── 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<EntityType> {
|
||||
// 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())],
|
||||
};
|
||||
|
||||
@@ -92,13 +92,13 @@ pub fn join_entities(entities: &[(Handle, &EntityType)]) -> Option<(Vec<Handle>,
|
||||
}
|
||||
|
||||
fn try_join_lines(lines: &[&(Handle, &EntityType)]) -> Option<(Vec<Handle>, Vec<EntityType>)> {
|
||||
// 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!()
|
||||
|
||||
@@ -290,7 +290,7 @@ fn lengthen_ellipse(ell: &EllipseEnt, pick_pt: Vec3, mode: &LenMode) -> Option<E
|
||||
|
||||
// Determine which end is closer to pick_pt (use DXF XY plane).
|
||||
let p_x = pick_pt.x as f64;
|
||||
let p_y = pick_pt.y as f64; // Y-up: world Z → DXF Y
|
||||
let p_y = pick_pt.y as f64;
|
||||
let pt_start_x = ell.center.x + a * t0.cos() * nx - b * t0.sin() * ny;
|
||||
let pt_start_y = ell.center.y + a * t0.cos() * ny + b * t0.sin() * nx;
|
||||
let pt_end_x = ell.center.x + a * t1.cos() * nx - b * t1.sin() * ny;
|
||||
@@ -339,7 +339,7 @@ fn lengthen_lwpoly(poly: &LwPolyline, pick_pt: Vec3, mode: &LenMode) -> Option<E
|
||||
|
||||
// Determine which end is closer to the pick point (DXF XY: pick_pt.x, pick_pt.z).
|
||||
let px = pick_pt.x as f64;
|
||||
let py = pick_pt.y as f64; // Y-up: world Z = DXF Y
|
||||
let py = pick_pt.y as f64;
|
||||
|
||||
let first = &poly.vertices[0];
|
||||
let last = &poly.vertices[n - 1];
|
||||
|
||||
@@ -132,7 +132,7 @@ pub fn t_to_rel(t_actual: f64, t0: f64, t1: f64) -> 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()
|
||||
}
|
||||
Reference in new issue
Block a user