refactor(grip): square-only marker set + oriented stretch handles

Phase 1 of bringing the grip vocabulary in line with the standard
CAD convention: drop the per-position-type shape encoding (Diamond
for translate, Circle for parameter) and use a plain square for
every endpoint / vertex / centre grip. The behavioural midpoint vs
vertex distinction stays — it lives on `GripDef::is_midpoint` and
drives `Translate` vs `Absolute` in the grip-edit dispatcher — only
the marker shape collapses.

- `GripShape` keeps `Square`, `Rectangle`, `Triangle`. Diamond /
  Circle are gone.
- `GripDef` gains `dir: Option<[f32; 2]>` — a world-XY direction
  vector consumed by `Rectangle` to orient the box along its
  segment.
- `entities::common` exposes `square_grip`, `center_grip` (same
  square marker, flagged as translate), `rectangle_grip(id, world,
  dir)` for oriented mid-segment handles, and a kept-for-Phase-2
  `triangle_grip`.
- `overlay::GripMarker` / `grips_to_screen[_paper]` thread `dir`
  through to the canvas painter; the Rectangle path rotates the
  box around the grip centre using the world-XY direction (flipping
  the sin for screen-Y).

Every existing `diamond_grip(id, w)` call becomes `center_grip(id,
w)` — the visual change is square-replacing-diamond at line
midpoints / arc midpoints / circle centres / ellipse centres /
viewport / hatch loop centroids / dimension grips / image corners /
multileader / ray / underlay / ole2frame / solid3d.

`LwPolyline` grows a stretch handle on every segment (straight or
arc), drawn as the new oriented rectangle. The grip-edit path
translates both segment endpoints for straight segments and
adjusts the arc bulge from the new midpoint for arc segments.
This commit is contained in:
Hakan Seven 2026-05-29 02:07:34 +03:00
commit 5755c2e130
20 changed files with 184 additions and 133 deletions

View file

@ -4,7 +4,7 @@ use truck_modeling::{builder, Edge, Point3, Wire};
use crate::command::EntityTransform;
use crate::entities::common::{
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
edit_prop as edit, parse_f64, rectangle_grip, ro_prop as ro, square_grip,
};
use crate::entities::traits::{TruckConvertible};
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
@ -295,21 +295,31 @@ fn grips(pline: &LwPolyline) -> Vec<GripDef> {
.map(|(i, v)| square_grip(i, Vec3::new(v.location.x as f32, v.location.y as f32, elev)))
.collect();
// Diamond midpoint grip for each arc segment
// One mid-segment stretch grip per segment (straight or arc). The
// marker is a small box rotated along the chord direction so the
// shape itself signals which way the segment runs.
for i in 0..seg_count {
let v0 = &pline.vertices[i];
if v0.bulge.abs() < 1e-9 {
continue;
}
let v1 = &pline.vertices[(i + 1) % n];
let mid = arc_midpoint(
[v0.location.x, v0.location.y],
[v1.location.x, v1.location.y],
v0.bulge,
);
out.push(diamond_grip(
let (mx, my) = if v0.bulge.abs() < 1e-9 {
(
(v0.location.x + v1.location.x) * 0.5,
(v0.location.y + v1.location.y) * 0.5,
)
} else {
let m = arc_midpoint(
[v0.location.x, v0.location.y],
[v1.location.x, v1.location.y],
v0.bulge,
);
(m[0], m[1])
};
let dx = (v1.location.x - v0.location.x) as f32;
let dy = (v1.location.y - v0.location.y) as f32;
out.push(rectangle_grip(
n + i,
Vec3::new(mid[0] as f32, mid[1] as f32, elev),
Vec3::new(mx as f32, my as f32, elev),
[dx, dy],
));
}
out
@ -355,7 +365,11 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
}
}
} else {
// Arc midpoint grip for segment (grip_id - n)
// Mid-segment stretch grip for segment (grip_id - n).
// Straight segments translate both endpoints by the drag delta
// (the shared vertices then carry along whichever adjacent
// segments share them). Arc segments adjust their bulge from
// the new midpoint position.
let seg = grip_id - n;
let count = if pline.is_closed {
n
@ -365,11 +379,31 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
if seg >= count {
return;
}
let i0 = seg;
let i1 = (seg + 1) % n;
let is_arc = pline.vertices[i0].bulge.abs() >= 1e-9;
if !is_arc {
let d = match apply {
GripApply::Translate(d) => [d.x as f64, d.y as f64],
GripApply::Absolute(p) => {
let old_mid = (
(pline.vertices[i0].location.x + pline.vertices[i1].location.x) * 0.5,
(pline.vertices[i0].location.y + pline.vertices[i1].location.y) * 0.5,
);
[p.x as f64 - old_mid.0, p.y as f64 - old_mid.1]
}
};
pline.vertices[i0].location.x += d[0];
pline.vertices[i0].location.y += d[1];
pline.vertices[i1].location.x += d[0];
pline.vertices[i1].location.y += d[1];
return;
}
let new_mid: [f64; 2] = match apply {
GripApply::Absolute(p) => [p.x as f64, p.y as f64],
GripApply::Translate(d) => {
let v0 = &pline.vertices[seg];
let v1 = &pline.vertices[(seg + 1) % n];
let v0 = &pline.vertices[i0];
let v1 = &pline.vertices[i1];
let old = arc_midpoint(
[v0.location.x, v0.location.y],
[v1.location.x, v1.location.y],
@ -379,15 +413,15 @@ fn apply_grip(pline: &mut LwPolyline, grip_id: usize, apply: GripApply) {
}
};
let p0 = [
pline.vertices[seg].location.x,
pline.vertices[seg].location.y,
pline.vertices[i0].location.x,
pline.vertices[i0].location.y,
];
let p1 = [
pline.vertices[(seg + 1) % n].location.x,
pline.vertices[(seg + 1) % n].location.y,
pline.vertices[i1].location.x,
pline.vertices[i1].location.y,
];
if let Some(new_bulge) = bulge_from_midpoint(p0, p1, new_mid) {
pline.vertices[seg].bulge = new_bulge.clamp(-1e6, 1e6);
pline.vertices[i0].bulge = new_bulge.clamp(-1e6, 1e6);
}
}
}