forked from 1b-agent/opencadstudio-upstream
fix(ucs): grid axis lines and grid snap follow the active UCS
Two leftovers from the UCS grid work: - The coloured X/Y/Z axis lines (draw_axes) were drawn at the world origin along world axes; they now run through the UCS origin along the UCS axes, matching the grid lines. - Grid snap rounded the cursor on the world XYZ lattice; it now rounds in the UCS grid frame (to-UCS, round, back-to-world) so it lands on the grid the user sees. The UCS origin (wire space) + rotation reach the snapper via DocumentTab::ucs_grid_basis; identity outside model space. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
2509396ba0
commit
481adba66b
4 files changed
+72
-36
No files matched your search
@@ -195,6 +195,23 @@ impl DocumentTab {
|
||||
self.scene.viewcube_ucs = self.ucs_xform().rotation_mat();
|
||||
}
|
||||
|
||||
/// Grid origin (render/wire space) and UCS→world rotation for grid snap and
|
||||
/// the grid overlay. Identity / origin-at-zero outside model space.
|
||||
pub(super) fn ucs_grid_basis(&self) -> (glam::Vec3, glam::Mat4) {
|
||||
if self.scene.current_layout != "Model" {
|
||||
return (glam::Vec3::ZERO, glam::Mat4::IDENTITY);
|
||||
}
|
||||
let xf = self.ucs_xform();
|
||||
let (o, ..) = xf.axes();
|
||||
let wo = self.scene.world_offset;
|
||||
let origin = glam::Vec3::new(
|
||||
o.x - wo[0] as f32,
|
||||
o.y - wo[1] as f32,
|
||||
o.z - wo[2] as f32,
|
||||
);
|
||||
(origin, xf.rotation_mat())
|
||||
}
|
||||
|
||||
pub(super) fn new_drawing(n: usize) -> Self {
|
||||
let mut scene = Scene::new();
|
||||
linetypes::populate_document(&mut scene.document);
|
||||
|
||||
+7
-3
@@ -2643,7 +2643,9 @@ impl OpenCADStudio {
|
||||
let all_wires = self.tabs[i].scene.hit_test_wires();
|
||||
// Grip drag has no single rubber-band origin for a perp foot.
|
||||
self.snapper.from_point = None;
|
||||
let snap_hit = self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds);
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
let snap_hit =
|
||||
self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds, go, gr);
|
||||
let mut snapped = snap_hit.map(|s| s.world).unwrap_or(raw);
|
||||
self.tabs[i].snap_result = snap_hit;
|
||||
if let Some(s) = self.tabs[i].snap_result.as_mut() {
|
||||
@@ -2817,9 +2819,10 @@ impl OpenCADStudio {
|
||||
bounds,
|
||||
)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
self.snapper.from_point = self.last_point;
|
||||
self.snapper
|
||||
.snap(cursor_world, p, &all_wires[..], view_proj, bounds)
|
||||
.snap(cursor_world, p, &all_wires[..], view_proj, bounds, go, gr)
|
||||
};
|
||||
|
||||
// Object Snap Tracking: update dwell, then align the cursor
|
||||
@@ -3445,8 +3448,9 @@ impl OpenCADStudio {
|
||||
self.snapper
|
||||
.snap_tangent_only(raw, p, &all_wires[..], vp_mat, bounds)
|
||||
} else {
|
||||
let (go, gr) = self.tabs[i].ucs_grid_basis();
|
||||
self.snapper.from_point = self.last_point;
|
||||
self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds)
|
||||
self.snapper.snap(raw, p, &all_wires[..], vp_mat, bounds, go, gr)
|
||||
};
|
||||
// snap.world is in paper-space (projected wire coords in MSPACE);
|
||||
// convert to model-space so commands receive consistent coordinates.
|
||||
|
||||
+23
-5
@@ -428,7 +428,16 @@ impl Snapper {
|
||||
dwell_acquired: false,
|
||||
from_point: None,
|
||||
};
|
||||
tmp.snap(cursor_world, cursor_screen, wires, view_proj, bounds)
|
||||
// Tangent-only: Grid is disabled here, so the grid basis is irrelevant.
|
||||
tmp.snap(
|
||||
cursor_world,
|
||||
cursor_screen,
|
||||
wires,
|
||||
view_proj,
|
||||
bounds,
|
||||
Vec3::ZERO,
|
||||
Mat4::IDENTITY,
|
||||
)
|
||||
}
|
||||
|
||||
/// Find the best snap candidate near the cursor.
|
||||
@@ -439,6 +448,10 @@ impl Snapper {
|
||||
wires: &[WireModel],
|
||||
view_proj: Mat4,
|
||||
bounds: Rectangle,
|
||||
// Grid origin (render/wire space) and UCS→world rotation, so grid snap
|
||||
// lands on the UCS grid the user sees. `(ZERO, IDENTITY)` = world grid.
|
||||
grid_origin: Vec3,
|
||||
grid_rot: Mat4,
|
||||
) -> Option<SnapResult> {
|
||||
if !self.snap_enabled {
|
||||
return None;
|
||||
@@ -727,10 +740,15 @@ impl Snapper {
|
||||
// ── Grid ───────────────────────────────────────────────────────────
|
||||
if self.is_on(SnapType::Grid) {
|
||||
let s = self.grid_spacing;
|
||||
let gx = (cursor_world.x / s).round() * s;
|
||||
let gy = (cursor_world.y / s).round() * s;
|
||||
let gz = (cursor_world.z / s).round() * s;
|
||||
try_pt(Vec3::new(gx, gy, gz), SnapType::Grid);
|
||||
// Round in the UCS grid frame, then map back to world.
|
||||
let ax = grid_rot.transform_vector3(Vec3::X);
|
||||
let ay = grid_rot.transform_vector3(Vec3::Y);
|
||||
let az = grid_rot.transform_vector3(Vec3::Z);
|
||||
let rel = cursor_world - grid_origin;
|
||||
let ux = (rel.dot(ax) / s).round() * s;
|
||||
let uy = (rel.dot(ay) / s).round() * s;
|
||||
let uz = (rel.dot(az) / s).round() * s;
|
||||
try_pt(grid_origin + ax * ux + ay * uy + az * uz, SnapType::Grid);
|
||||
}
|
||||
|
||||
// ── Tangent ────────────────────────────────────────────────────────
|
||||
|
||||
+25
-28
@@ -981,14 +981,21 @@ fn draw_grid(
|
||||
);
|
||||
}
|
||||
|
||||
// World-space axes always drawn on top of the grid lines.
|
||||
// Coloured axes drawn on top of the grid lines, along the same UCS basis.
|
||||
let extent = (min1.abs().max(max1.abs()).max(min2.abs()).max(max2.abs()) + s) * 1.5;
|
||||
draw_axes(frame, vp, bounds, extent.max(10.0));
|
||||
draw_axes(frame, vp, bounds, extent.max(10.0), grid_origin, grid_axes);
|
||||
}
|
||||
|
||||
// ── Coloured world-space axes ─────────────────────────────────────────────
|
||||
// ── Coloured UCS axes ──────────────────────────────────────────────────────
|
||||
|
||||
fn draw_axes(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle, extent: f32) {
|
||||
fn draw_axes(
|
||||
frame: &mut canvas::Frame,
|
||||
vp: Mat4,
|
||||
bounds: iced::Rectangle,
|
||||
extent: f32,
|
||||
origin: Vec3,
|
||||
axes: (Vec3, Vec3, Vec3),
|
||||
) {
|
||||
let w2s = |world: Vec3| -> Point {
|
||||
let ndc = vp.project_point3(world);
|
||||
Point::new(
|
||||
@@ -997,35 +1004,25 @@ fn draw_axes(frame: &mut canvas::Frame, vp: Mat4, bounds: iced::Rectangle, exten
|
||||
)
|
||||
};
|
||||
let e = extent;
|
||||
let (ax, ay, az) = axes;
|
||||
let axis_stroke = |r: f32, g: f32, b: f32| canvas::Stroke {
|
||||
width: 1.5,
|
||||
style: canvas::Style::Solid(Color { r, g, b, a: 0.85 }),
|
||||
..Default::default()
|
||||
};
|
||||
// X — red
|
||||
frame.stroke(
|
||||
&canvas::Path::new(|p| {
|
||||
p.move_to(w2s(Vec3::new(-e, 0.0, 0.0)));
|
||||
p.line_to(w2s(Vec3::new(e, 0.0, 0.0)));
|
||||
}),
|
||||
axis_stroke(0.90, 0.20, 0.20),
|
||||
);
|
||||
// Y — green
|
||||
frame.stroke(
|
||||
&canvas::Path::new(|p| {
|
||||
p.move_to(w2s(Vec3::new(0.0, -e, 0.0)));
|
||||
p.line_to(w2s(Vec3::new(0.0, e, 0.0)));
|
||||
}),
|
||||
axis_stroke(0.20, 0.85, 0.20),
|
||||
);
|
||||
// Z — blue
|
||||
frame.stroke(
|
||||
&canvas::Path::new(|p| {
|
||||
p.move_to(w2s(Vec3::new(0.0, 0.0, -e)));
|
||||
p.line_to(w2s(Vec3::new(0.0, 0.0, e)));
|
||||
}),
|
||||
axis_stroke(0.20, 0.40, 0.90),
|
||||
);
|
||||
// Axes run through the UCS origin along the UCS axis directions.
|
||||
let mut line = |dir: Vec3, r: f32, g: f32, b: f32| {
|
||||
frame.stroke(
|
||||
&canvas::Path::new(|p| {
|
||||
p.move_to(w2s(origin - dir * e));
|
||||
p.line_to(w2s(origin + dir * e));
|
||||
}),
|
||||
axis_stroke(r, g, b),
|
||||
);
|
||||
};
|
||||
line(ax, 0.90, 0.20, 0.20); // X — red
|
||||
line(ay, 0.20, 0.85, 0.20); // Y — green
|
||||
line(az, 0.20, 0.40, 0.90); // Z — blue
|
||||
}
|
||||
|
||||
// ── UCS icon ──────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user