forked from 1b-agent/opencadstudio-upstream
feat(ucs): ortho/polar constrain in the active UCS plane
Ortho and polar drawing constraints snapped to world X/Y; under an active UCS they now snap to the UCS axes, routed through the shared UcsXform (identity = unchanged world-XY behaviour). Applied at all three input sites (grip drag, command point, command preview). Typed coordinates already flow through the converter via ucs_to_wcs / ucs_rotate_vec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
4aa1e90dcf
commit
746a2c97be
2 files changed
+41
-21
No files matched your search
+32
-18
@@ -169,21 +169,34 @@ pub(super) fn grid_plane_from_camera(pitch: f32, yaw: f32) -> GridPlane {
|
||||
|
||||
// ── Drawing constraint helpers ─────────────────────────────────────────────
|
||||
|
||||
/// Constrain `pt` to the nearest 90° direction from `base` (XY plane, Z-up).
|
||||
pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3) -> glam::Vec3 {
|
||||
let dx = (pt.x - base.x).abs();
|
||||
let dy = (pt.y - base.y).abs();
|
||||
if dx >= dy {
|
||||
glam::Vec3::new(pt.x, base.y, pt.z)
|
||||
/// Constrain `pt` to the nearest 90° direction from `base`, in the active UCS
|
||||
/// plane — ortho follows the user's coordinate system, not world axes. `xf` is
|
||||
/// identity for plain WCS, so the world-XY behaviour is unchanged there.
|
||||
pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3, xf: &UcsXform) -> glam::Vec3 {
|
||||
let p = xf.to_ucs(pt);
|
||||
let b = xf.to_ucs(base);
|
||||
let dx = (p.x - b.x).abs();
|
||||
let dy = (p.y - b.y).abs();
|
||||
let c = if dx >= dy {
|
||||
glam::Vec3::new(p.x, b.y, p.z)
|
||||
} else {
|
||||
glam::Vec3::new(base.x, pt.y, pt.z)
|
||||
}
|
||||
glam::Vec3::new(b.x, p.y, p.z)
|
||||
};
|
||||
xf.to_wcs(c)
|
||||
}
|
||||
|
||||
/// Constrain `pt` to the nearest polar angle multiple from `base` (XY plane, Z-up).
|
||||
pub(super) fn polar_constrain(pt: glam::Vec3, base: glam::Vec3, step_deg: f32) -> glam::Vec3 {
|
||||
let dx = pt.x - base.x;
|
||||
let dy = pt.y - base.y;
|
||||
/// Constrain `pt` to the nearest polar angle multiple from `base`, measured in
|
||||
/// the active UCS plane (identity `xf` = world XY, Z-up).
|
||||
pub(super) fn polar_constrain(
|
||||
pt: glam::Vec3,
|
||||
base: glam::Vec3,
|
||||
step_deg: f32,
|
||||
xf: &UcsXform,
|
||||
) -> glam::Vec3 {
|
||||
let p = xf.to_ucs(pt);
|
||||
let b = xf.to_ucs(base);
|
||||
let dx = p.x - b.x;
|
||||
let dy = p.y - b.y;
|
||||
let dist = (dx * dx + dy * dy).sqrt();
|
||||
if dist < 1e-6 {
|
||||
return pt;
|
||||
@@ -191,11 +204,11 @@ pub(super) fn polar_constrain(pt: glam::Vec3, base: glam::Vec3, step_deg: f32) -
|
||||
let step = step_deg.to_radians();
|
||||
let angle = dy.atan2(dx);
|
||||
let snapped = (angle / step).round() * step;
|
||||
glam::Vec3::new(
|
||||
base.x + dist * snapped.cos(),
|
||||
base.y + dist * snapped.sin(),
|
||||
pt.z,
|
||||
)
|
||||
xf.to_wcs(glam::Vec3::new(
|
||||
b.x + dist * snapped.cos(),
|
||||
b.y + dist * snapped.sin(),
|
||||
p.z,
|
||||
))
|
||||
}
|
||||
|
||||
/// Polar constraint that only engages when the cursor is within `tol_px`
|
||||
@@ -208,8 +221,9 @@ pub(super) fn polar_constrain_near(
|
||||
view_proj: glam::Mat4,
|
||||
bounds: iced::Rectangle,
|
||||
tol_px: f32,
|
||||
xf: &UcsXform,
|
||||
) -> glam::Vec3 {
|
||||
let snapped = polar_constrain(pt, base, step_deg);
|
||||
let snapped = polar_constrain(pt, base, step_deg, xf);
|
||||
let to_screen = |w: glam::Vec3| {
|
||||
let ndc = view_proj.project_point3(w);
|
||||
(
|
||||
|
||||
+9
-3
@@ -2653,8 +2653,9 @@ impl OpenCADStudio {
|
||||
|
||||
if snap_hit.is_none() {
|
||||
let base = grip.origin_world;
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
snapped = ortho_constrain(snapped, base);
|
||||
snapped = ortho_constrain(snapped, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
snapped = polar_constrain_near(
|
||||
snapped,
|
||||
@@ -2663,6 +2664,7 @@ impl OpenCADStudio {
|
||||
vp_mat,
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2871,8 +2873,9 @@ impl OpenCADStudio {
|
||||
.is_some_and(|s| s.snap_type != crate::snap::SnapType::Grid);
|
||||
if !osnap_locked {
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt, base);
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt,
|
||||
@@ -2881,6 +2884,7 @@ impl OpenCADStudio {
|
||||
view_proj,
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3477,8 +3481,9 @@ impl OpenCADStudio {
|
||||
// Object snap wins over ortho/polar — a snapped point
|
||||
// commits as-is. Grid snap still combines. (#132)
|
||||
if let Some(base) = self.last_point {
|
||||
let ucs_xf = self.tabs[i].ucs_xform();
|
||||
if self.ortho_mode {
|
||||
pt = ortho_constrain(pt, base);
|
||||
pt = ortho_constrain(pt, base, &ucs_xf);
|
||||
} else if self.polar_mode {
|
||||
pt = polar_constrain_near(
|
||||
pt,
|
||||
@@ -3487,6 +3492,7 @@ impl OpenCADStudio {
|
||||
vp_mat,
|
||||
bounds,
|
||||
self.snapper.osnap_radius_px,
|
||||
&ucs_xf,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user