forked from 1b-agent/opencadstudio-upstream
feat(input): relative coordinate entry (@/#) and DYN default — #26
Koordinat girişi artık göreli/mutlak ayrımını destekliyor: - `@x,y[,z]` → son girilen noktaya göreli (UCS eksenlerinde döndürülür) - `#x,y[,z]` → her zaman mutlak (DYN durumundan bağımsız) - ön ek yok → DYN açıksa göreli, kapalıysa mutlak İlk noktada referans olmadığından göreli giriş mutlağa düşer. Çözülen WCS noktası last_point olarak saklanıyor, böylece ardışık göreli girişler zincirleniyor. parse_coord artık (Vec3, CoordKind) döndürüyor; relative offset için ucs_rotate_vec eklendi (origin translation olmadan UCS rotasyonu). UCS ORIGIN komutu prefix'i yok sayıp mutlak kalıyor. Ayrıca ViewCube snap refactor'undan kalan kullanılmayan kod temizlendi: angle_close, active_viewport_yaw_pitch, CubeRegion::snap_angles. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
451239c4f9
commit
874226bd43
5 files changed
+77
-81
No files matched your search
+4
-2
@@ -3256,8 +3256,10 @@ impl OpenCADStudio {
|
||||
// UCS ORIGIN x,y,z — shift the active UCS origin, keep axes
|
||||
"ORIGIN" | "O" => {
|
||||
let coord_str = parts.get(2).copied().unwrap_or("");
|
||||
if let Some(pt) = super::helpers::parse_coord(coord_str) {
|
||||
// `pt` is in current UCS space; convert to WCS
|
||||
if let Some((pt, _)) = super::helpers::parse_coord(coord_str) {
|
||||
// `pt` is in current UCS space; convert to WCS.
|
||||
// The @/# relative-coordinate prefix is ignored
|
||||
// here — a UCS origin is always absolute.
|
||||
let wcs_origin = if let Some(ref ucs) = self.tabs[i].active_ucs {
|
||||
ucs_to_wcs(pt, ucs)
|
||||
} else {
|
||||
|
||||
+38
-17
@@ -4,23 +4,54 @@ use acadrust::tables::Ucs;
|
||||
|
||||
// ── Coordinate parsing ─────────────────────────────────────────────────────
|
||||
|
||||
/// Parse a typed coordinate string into a world-space Vec3.
|
||||
/// How a typed coordinate should be interpreted relative to the last
|
||||
/// input point.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(super) enum CoordKind {
|
||||
/// `@x,y` prefix — offset from the last input point.
|
||||
Relative,
|
||||
/// `#x,y` prefix — world/UCS absolute, overriding DYN.
|
||||
Absolute,
|
||||
/// No prefix — the caller decides (DYN on → relative, off → absolute).
|
||||
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)
|
||||
/// Separators: comma or semicolon; decimal point or decimal comma.
|
||||
pub(super) fn parse_coord(text: &str) -> Option<glam::Vec3> {
|
||||
let parts: Vec<f32> = text
|
||||
/// 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::Vec3, CoordKind)> {
|
||||
let trimmed = text.trim();
|
||||
let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') {
|
||||
(CoordKind::Relative, r)
|
||||
} else if let Some(r) = trimmed.strip_prefix('#') {
|
||||
(CoordKind::Absolute, r)
|
||||
} else {
|
||||
(CoordKind::Default, trimmed)
|
||||
};
|
||||
let parts: Vec<f32> = rest
|
||||
.split(|c| c == ',' || c == ';')
|
||||
.map(|s| s.trim().replace(',', "."))
|
||||
.map(|s| s.trim())
|
||||
.filter_map(|s| s.parse().ok())
|
||||
.collect();
|
||||
match parts.as_slice() {
|
||||
[x, y] => Some(glam::Vec3::new(*x, *y, 0.0)),
|
||||
[x, y, z] => Some(glam::Vec3::new(*x, *y, *z)),
|
||||
[x, y] => Some((glam::Vec3::new(*x, *y, 0.0), kind)),
|
||||
[x, y, z] => Some((glam::Vec3::new(*x, *y, *z), kind)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotate a UCS-local offset into WCS without applying the origin
|
||||
/// translation — used for relative coordinate entry, where only the
|
||||
/// axis orientation matters, not the UCS origin.
|
||||
pub(super) fn ucs_rotate_vec(offset: glam::Vec3, ucs: &Ucs) -> glam::Vec3 {
|
||||
let x = glam::Vec3::new(ucs.x_axis.x as f32, ucs.x_axis.y as f32, ucs.x_axis.z as f32);
|
||||
let y = glam::Vec3::new(ucs.y_axis.x as f32, ucs.y_axis.y as f32, ucs.y_axis.z as f32);
|
||||
let z = ucs_z_axis(ucs);
|
||||
x * offset.x + y * offset.y + z * offset.z
|
||||
}
|
||||
|
||||
// ── UCS ↔ WCS transforms ───────────────────────────────────────────────────
|
||||
|
||||
/// Convert a point from UCS local coordinates to WCS.
|
||||
@@ -72,16 +103,6 @@ pub(super) fn ucs_rotated_z(origin: glam::Vec3, angle_z: f32) -> Ucs {
|
||||
ucs
|
||||
}
|
||||
|
||||
pub(super) fn angle_close(a: f32, b: f32, tol: f32) -> bool {
|
||||
let diff = (a - b).rem_euclid(std::f32::consts::TAU);
|
||||
let diff = if diff > std::f32::consts::PI {
|
||||
diff - std::f32::consts::TAU
|
||||
} else {
|
||||
diff
|
||||
};
|
||||
diff.abs() < tol
|
||||
}
|
||||
|
||||
// ── Grid plane detection ───────────────────────────────────────────────────
|
||||
|
||||
/// Choose the grid plane whose normal is most aligned with the camera view direction.
|
||||
|
||||
+35
-7
@@ -1,5 +1,6 @@
|
||||
use super::helpers::{
|
||||
angle_close, ortho_constrain, parse_coord, polar_constrain, ucs_to_wcs, ucs_z_axis,
|
||||
ortho_constrain, parse_coord, polar_constrain, ucs_rotate_vec, ucs_to_wcs, ucs_z_axis,
|
||||
CoordKind,
|
||||
};
|
||||
use super::{Message, OpenCADStudio, POLY_START_DELAY_MS};
|
||||
use crate::modules::ModuleEvent;
|
||||
@@ -960,13 +961,40 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if let Some(ucs_pt) = parse_coord(&text) {
|
||||
// Typed coordinates are in active UCS space; convert to WCS.
|
||||
let wcs_pt = if let Some(ref ucs) = self.tabs[i].active_ucs {
|
||||
ucs_to_wcs(ucs_pt, ucs)
|
||||
} else {
|
||||
ucs_pt
|
||||
if let Some((coord, kind)) = parse_coord(&text) {
|
||||
// Resolve relative vs absolute. `@` forces relative,
|
||||
// `#` forces absolute, no prefix follows DYN: when
|
||||
// dynamic input is on, bare coordinates are relative
|
||||
// to the last point (issue #26).
|
||||
// `@` forces relative, `#` forces absolute, no
|
||||
// prefix follows DYN (on → relative). The very
|
||||
// first point has no reference, so relative falls
|
||||
// back to absolute.
|
||||
let want_relative = match kind {
|
||||
CoordKind::Relative => true,
|
||||
CoordKind::Absolute => false,
|
||||
CoordKind::Default => self.dyn_input,
|
||||
};
|
||||
let ucs = self.tabs[i].active_ucs.clone();
|
||||
let wcs_pt = match (want_relative, self.last_point) {
|
||||
(true, Some(base)) => {
|
||||
// Offset from the last point, rotated by the
|
||||
// UCS axes (no origin translation).
|
||||
let offset = match &ucs {
|
||||
Some(u) => ucs_rotate_vec(coord, u),
|
||||
None => coord,
|
||||
};
|
||||
base + offset
|
||||
}
|
||||
_ => {
|
||||
// Absolute: typed coordinates are in active UCS.
|
||||
match &ucs {
|
||||
Some(u) => ucs_to_wcs(coord, u),
|
||||
None => coord,
|
||||
}
|
||||
}
|
||||
};
|
||||
self.last_point = Some(wcs_pt);
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt));
|
||||
if let Some(r) = result {
|
||||
return self.apply_cmd_result(r);
|
||||
|
||||
@@ -2211,13 +2211,6 @@ impl Scene {
|
||||
self.camera.borrow().view_rotation_mat()
|
||||
}
|
||||
|
||||
/// Return (yaw, pitch) of the active viewport's camera, or None if PSPACE.
|
||||
pub fn active_viewport_yaw_pitch(&self) -> Option<(f32, f32)> {
|
||||
let h = self.active_viewport?;
|
||||
let cam = self.camera_for_viewport(h)?;
|
||||
Some((cam.yaw, cam.pitch))
|
||||
}
|
||||
|
||||
/// Return the handle of the user viewport whose bounding rectangle contains
|
||||
/// the given paper-space point, or `None` if no viewport matches.
|
||||
pub fn viewport_at_paper_point(&self, px: f32, py: f32) -> Option<Handle> {
|
||||
|
||||
@@ -83,54 +83,6 @@ impl CubeRegion {
|
||||
glam::Vec3::new(c[0], c[1], c[2]).normalize_or(glam::Vec3::Z)
|
||||
}
|
||||
|
||||
/// Returns `(yaw, pitch, roll)` for the canonical camera orientation
|
||||
/// pointed at this region.
|
||||
///
|
||||
/// `yaw` (rotate around world Z) and `pitch` (tilt toward world Z)
|
||||
/// place the camera on the right side of the cube; `roll` rotates
|
||||
/// around the camera's view axis so edge views land with the edge
|
||||
/// vertical on screen instead of horizontal. Faces and corners
|
||||
/// keep `roll = 0` (world up stays up).
|
||||
pub fn snap_angles(self) -> (f32, f32, f32) {
|
||||
use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, PI};
|
||||
let iso = (1.0_f32 / 2.0_f32.sqrt()).atan();
|
||||
// For the 8 horizontal-cube edges (top + bottom rings), roll by
|
||||
// ±π/2 around the camera's view axis so the edge sits vertical
|
||||
// on screen — TOP face on one half, the side face on the other.
|
||||
// The 4 vertical edges (FRONT-RIGHT etc.) already run along Z
|
||||
// and read vertical with roll = 0.
|
||||
let r = FRAC_PI_2;
|
||||
match self.id() {
|
||||
0 => (0.0, FRAC_PI_2, 0.0),
|
||||
1 => (0.0, -FRAC_PI_2, 0.0),
|
||||
2 => (0.0, 0.0, 0.0), // FRONT
|
||||
3 => (PI, 0.0, 0.0), // BACK
|
||||
4 => (FRAC_PI_2, 0.0, 0.0), // RIGHT
|
||||
5 => (-FRAC_PI_2, 0.0, 0.0), // LEFT
|
||||
6 => (0.0, FRAC_PI_4, r), // EDGE_TOP_FRONT
|
||||
7 => (PI, FRAC_PI_4, r), // EDGE_TOP_BACK
|
||||
8 => (FRAC_PI_2, FRAC_PI_4, r), // EDGE_TOP_RIGHT
|
||||
9 => (-FRAC_PI_2, FRAC_PI_4, r), // EDGE_TOP_LEFT
|
||||
10 => (0.0, -FRAC_PI_4, r), // EDGE_BOT_FRONT
|
||||
11 => (PI, -FRAC_PI_4, r), // EDGE_BOT_BACK
|
||||
12 => (FRAC_PI_2, -FRAC_PI_4, r), // EDGE_BOT_RIGHT
|
||||
13 => (-FRAC_PI_2, -FRAC_PI_4, r), // EDGE_BOT_LEFT
|
||||
14 => (FRAC_PI_4, 0.0, 0.0), // EDGE_FRONT_RIGHT
|
||||
15 => (-FRAC_PI_4, 0.0, 0.0), // EDGE_FRONT_LEFT
|
||||
16 => (PI * 0.75, 0.0, 0.0), // EDGE_BACK_RIGHT
|
||||
17 => (-PI * 0.75, 0.0, 0.0), // EDGE_BACK_LEFT
|
||||
18 => (FRAC_PI_4, iso, 0.0), // CORNER_TPF_R
|
||||
19 => (-FRAC_PI_4, iso, 0.0), // CORNER_TPF_L
|
||||
20 => (PI * 0.75, iso, 0.0), // CORNER_TBK_R
|
||||
21 => (-PI * 0.75, iso, 0.0), // CORNER_TBK_L
|
||||
22 => (FRAC_PI_4, -iso, 0.0), // CORNER_BTF_R
|
||||
23 => (-FRAC_PI_4, -iso, 0.0), // CORNER_BTF_L
|
||||
24 => (PI * 0.75, -iso, 0.0), // CORNER_BBK_R
|
||||
25 => (-PI * 0.75, -iso, 0.0), // CORNER_BBK_L
|
||||
_ => (0.0, 0.0, 0.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opposite(self) -> CubeRegion {
|
||||
match self {
|
||||
CubeRegion::Face(FACE_TOP) => CubeRegion::Face(FACE_BOTTOM),
|
||||
|
||||
Reference in new issue
Block a user