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:
parent
1e33a9049c
commit
5755c2e130
20 changed files with 184 additions and 133 deletions
|
|
@ -299,7 +299,7 @@ impl OpenCADStudio {
|
|||
};
|
||||
screen_grips
|
||||
.into_iter()
|
||||
.filter(|(_, screen, _, _)| {
|
||||
.filter(|(_, screen, _, _, _)| {
|
||||
screen.x.is_finite()
|
||||
&& screen.y.is_finite()
|
||||
&& screen.x >= -bounds.width
|
||||
|
|
@ -307,7 +307,7 @@ impl OpenCADStudio {
|
|||
&& screen.y >= -bounds.height
|
||||
&& screen.y <= bounds.height * 2.0
|
||||
})
|
||||
.map(|(grip_id, screen, _is_midpoint, shape)| {
|
||||
.map(|(grip_id, screen, _is_midpoint, shape, dir)| {
|
||||
let is_hot = tab
|
||||
.active_grip
|
||||
.as_ref()
|
||||
|
|
@ -316,6 +316,7 @@ impl OpenCADStudio {
|
|||
pos: screen,
|
||||
shape,
|
||||
is_hot,
|
||||
dir,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use glam::Vec3;
|
|||
use truck_modeling::{builder, Point3};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, parse_f64, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, square_grip};
|
||||
use crate::entities::traits::TruckConvertible;
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -118,10 +118,10 @@ fn grips(arc: &Arc) -> Vec<GripDef> {
|
|||
let ea = arc.end_angle as f32;
|
||||
let ma = sa + angle_span(sa, ea) * 0.5;
|
||||
vec![
|
||||
diamond_grip(0, ctr),
|
||||
center_grip(0, ctr),
|
||||
square_grip(1, ctr + Vec3::new(r * sa.cos(), r * sa.sin(), 0.0)),
|
||||
square_grip(2, ctr + Vec3::new(r * ea.cos(), r * ea.sin(), 0.0)),
|
||||
diamond_grip(3, ctr + Vec3::new(r * ma.cos(), r * ma.sin(), 0.0)),
|
||||
center_grip(3, ctr + Vec3::new(r * ma.cos(), r * ma.sin(), 0.0)),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use truck_modeling::{builder, Point3, Wire};
|
|||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
};
|
||||
use crate::entities::traits::TruckConvertible;
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
|
|
@ -119,7 +119,7 @@ fn grips(circle: &Circle) -> Vec<GripDef> {
|
|||
);
|
||||
let r = circle.radius as f32;
|
||||
vec![
|
||||
diamond_grip(0, ctr),
|
||||
center_grip(0, ctr),
|
||||
square_grip(1, ctr + Vec3::new(r, 0.0, 0.0)),
|
||||
square_grip(2, ctr + Vec3::new(0.0, r, 0.0)),
|
||||
square_grip(3, ctr - Vec3::new(r, 0.0, 0.0)),
|
||||
|
|
|
|||
|
|
@ -127,44 +127,43 @@ pub fn square_grip(id: usize, world: Vec3) -> GripDef {
|
|||
world,
|
||||
is_midpoint: false,
|
||||
shape: GripShape::Square,
|
||||
dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diamond_grip(id: usize, world: Vec3) -> GripDef {
|
||||
/// Centre / translate grip — same square marker as a vertex grip but
|
||||
/// flagged as a "whole-object move" handle for the grip-edit code.
|
||||
pub fn center_grip(id: usize, world: Vec3) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: true,
|
||||
shape: GripShape::Diamond,
|
||||
shape: GripShape::Square,
|
||||
dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Mid-segment stretch grip oriented along `dir` (the segment's in-plane
|
||||
/// world-XY direction). Drawn as a small rectangle elongated along the
|
||||
/// segment so the affordance reads as "stretch perpendicular".
|
||||
pub fn rectangle_grip(id: usize, world: Vec3, dir: [f32; 2]) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: true,
|
||||
shape: GripShape::Rectangle,
|
||||
dir: Some(dir),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn triangle_grip(id: usize, world: Vec3) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: false,
|
||||
shape: GripShape::Triangle,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn rectangle_grip(id: usize, world: Vec3) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: true,
|
||||
shape: GripShape::Rectangle,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn circle_grip(id: usize, world: Vec3) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
world,
|
||||
is_midpoint: false,
|
||||
shape: GripShape::Circle,
|
||||
dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use glam::Vec3;
|
|||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -594,45 +594,45 @@ impl Grippable for Dimension {
|
|||
match self {
|
||||
Dimension::Linear(d) => vec![
|
||||
square_grip(0, v3(&d.first_point)),
|
||||
diamond_grip(1, v3(&d.second_point)),
|
||||
diamond_grip(2, v3(&d.definition_point)),
|
||||
diamond_grip(3, text),
|
||||
center_grip(1, v3(&d.second_point)),
|
||||
center_grip(2, v3(&d.definition_point)),
|
||||
center_grip(3, text),
|
||||
],
|
||||
Dimension::Aligned(d) => vec![
|
||||
square_grip(0, v3(&d.first_point)),
|
||||
diamond_grip(1, v3(&d.second_point)),
|
||||
diamond_grip(2, v3(&d.definition_point)),
|
||||
diamond_grip(3, text),
|
||||
center_grip(1, v3(&d.second_point)),
|
||||
center_grip(2, v3(&d.definition_point)),
|
||||
center_grip(3, text),
|
||||
],
|
||||
Dimension::Radius(d) => vec![
|
||||
square_grip(0, v3(&d.angle_vertex)),
|
||||
diamond_grip(1, v3(&d.definition_point)),
|
||||
diamond_grip(2, text),
|
||||
center_grip(1, v3(&d.definition_point)),
|
||||
center_grip(2, text),
|
||||
],
|
||||
Dimension::Diameter(d) => vec![
|
||||
square_grip(0, v3(&d.angle_vertex)),
|
||||
diamond_grip(1, v3(&d.definition_point)),
|
||||
diamond_grip(2, text),
|
||||
center_grip(1, v3(&d.definition_point)),
|
||||
center_grip(2, text),
|
||||
],
|
||||
Dimension::Angular2Ln(d) => vec![
|
||||
square_grip(0, v3(&d.angle_vertex)),
|
||||
diamond_grip(1, v3(&d.first_point)),
|
||||
diamond_grip(2, v3(&d.second_point)),
|
||||
diamond_grip(3, v3(&d.definition_point)),
|
||||
diamond_grip(4, text),
|
||||
center_grip(1, v3(&d.first_point)),
|
||||
center_grip(2, v3(&d.second_point)),
|
||||
center_grip(3, v3(&d.definition_point)),
|
||||
center_grip(4, text),
|
||||
],
|
||||
Dimension::Angular3Pt(d) => vec![
|
||||
square_grip(0, v3(&d.angle_vertex)),
|
||||
diamond_grip(1, v3(&d.first_point)),
|
||||
diamond_grip(2, v3(&d.second_point)),
|
||||
diamond_grip(3, v3(&d.definition_point)),
|
||||
diamond_grip(4, text),
|
||||
center_grip(1, v3(&d.first_point)),
|
||||
center_grip(2, v3(&d.second_point)),
|
||||
center_grip(3, v3(&d.definition_point)),
|
||||
center_grip(4, text),
|
||||
],
|
||||
Dimension::Ordinate(d) => vec![
|
||||
square_grip(0, v3(&d.definition_point)),
|
||||
diamond_grip(1, v3(&d.feature_location)),
|
||||
diamond_grip(2, v3(&d.leader_endpoint)),
|
||||
diamond_grip(3, text),
|
||||
center_grip(1, v3(&d.feature_location)),
|
||||
center_grip(2, v3(&d.leader_endpoint)),
|
||||
center_grip(3, text),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use glam::Vec3;
|
|||
use truck_modeling::{builder, BSplineCurve, Curve, Edge, KnotVec, Point3, Wire};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::TruckConvertible;
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -164,7 +164,7 @@ fn grips(ell: &Ellipse) -> Vec<GripDef> {
|
|||
ell.center.z as f32,
|
||||
);
|
||||
vec![
|
||||
diamond_grip(0, ctr),
|
||||
center_grip(0, ctr),
|
||||
square_grip(1, maj),
|
||||
square_grip(2, min),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use acadrust::entities::{BoundaryEdge, Hatch};
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::traits::{Grippable, FallbackTess, PropertyEditable, Transformable};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::tess_util::{arc_segments, arc_signed_span, wire_chord_tol, FallbackGeometry};
|
||||
|
|
@ -182,31 +182,31 @@ impl Grippable for Hatch {
|
|||
match edge {
|
||||
BoundaryEdge::Polyline(p) => {
|
||||
for v in &p.vertices {
|
||||
out.push(diamond_grip(id, Vec3::new(v.x as f32, v.y as f32, elev)));
|
||||
out.push(center_grip(id, Vec3::new(v.x as f32, v.y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
BoundaryEdge::Line(l) => {
|
||||
out.push(diamond_grip(
|
||||
out.push(center_grip(
|
||||
id,
|
||||
Vec3::new(l.start.x as f32, l.start.y as f32, elev),
|
||||
));
|
||||
id += 1;
|
||||
out.push(diamond_grip(
|
||||
out.push(center_grip(
|
||||
id,
|
||||
Vec3::new(l.end.x as f32, l.end.y as f32, elev),
|
||||
));
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::CircularArc(a) => {
|
||||
out.push(diamond_grip(
|
||||
out.push(center_grip(
|
||||
id,
|
||||
Vec3::new(a.center.x as f32, a.center.y as f32, elev),
|
||||
));
|
||||
id += 1;
|
||||
}
|
||||
BoundaryEdge::EllipticArc(e) => {
|
||||
out.push(diamond_grip(
|
||||
out.push(center_grip(
|
||||
id,
|
||||
Vec3::new(e.center.x as f32, e.center.y as f32, elev),
|
||||
));
|
||||
|
|
@ -219,7 +219,7 @@ impl Grippable for Hatch {
|
|||
s.control_points.iter().map(|p| [p.x, p.y]).collect()
|
||||
};
|
||||
for [x, y] in pts {
|
||||
out.push(diamond_grip(id, Vec3::new(x as f32, y as f32, elev)));
|
||||
out.push(center_grip(id, Vec3::new(x as f32, y as f32, elev)));
|
||||
id += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use acadrust::Entity;
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
|
|
@ -98,7 +98,7 @@ fn grips(leader: &Leader) -> Vec<GripDef> {
|
|||
let sum = leader.vertices.iter().fold(Vec3::ZERO, |acc, v| {
|
||||
acc + Vec3::new(v.x as f32, v.y as f32, v.z as f32)
|
||||
});
|
||||
grips.push(diamond_grip(n, sum / n as f32));
|
||||
grips.push(center_grip(n, sum / n as f32));
|
||||
}
|
||||
|
||||
grips
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use truck_modeling::{builder, Point3};
|
|||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
};
|
||||
use crate::entities::traits::TruckConvertible;
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
|
|
@ -74,7 +74,7 @@ fn grips(line: &Line) -> Vec<GripDef> {
|
|||
);
|
||||
let e = Vec3::new(line.end.x as f32, line.end.y as f32, line.end.z as f32);
|
||||
let m = (s + e) * 0.5;
|
||||
vec![square_grip(0, s), square_grip(1, e), diamond_grip(2, m)]
|
||||
vec![square_grip(0, s), square_grip(1, e), center_grip(2, m)]
|
||||
}
|
||||
|
||||
fn properties(line: &Line) -> PropSection {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub(crate) fn mleader_v_anchor(attach: TextAttachmentType) -> MTextVAnchor {
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
|
|
@ -310,7 +310,7 @@ fn grips(ml: &MultiLeader) -> Vec<GripDef> {
|
|||
|
||||
if ml.content_type == LeaderContentType::MText {
|
||||
let tl = &ml.context.text_location;
|
||||
result.push(diamond_grip(
|
||||
result.push(center_grip(
|
||||
id,
|
||||
Vec3::new(tl.x as f32, tl.y as f32, tl.z as f32),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use acadrust::entities::{Ole2Frame, OleObjectType};
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -70,7 +70,7 @@ fn grips(ole: &Ole2Frame) -> Vec<GripDef> {
|
|||
vec![
|
||||
square_grip(0, ul),
|
||||
square_grip(1, lr),
|
||||
diamond_grip(2, center),
|
||||
center_grip(2, center),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use acadrust::entities::{RasterImage, Wipeout};
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
|
|
@ -140,9 +140,9 @@ impl Grippable for RasterImage {
|
|||
);
|
||||
vec![
|
||||
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
|
||||
diamond_grip(1, Vec3::from(to_f32_3(corners[1]))),
|
||||
diamond_grip(2, Vec3::from(to_f32_3(corners[2]))),
|
||||
diamond_grip(3, Vec3::from(to_f32_3(corners[3]))),
|
||||
center_grip(1, Vec3::from(to_f32_3(corners[1]))),
|
||||
center_grip(2, Vec3::from(to_f32_3(corners[2]))),
|
||||
center_grip(3, Vec3::from(to_f32_3(corners[3]))),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -346,7 +346,7 @@ impl Grippable for Wipeout {
|
|||
if i == 0 {
|
||||
square_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz))
|
||||
} else {
|
||||
diamond_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz))
|
||||
center_grip(i, Vec3::new(ox + wx, oy + wy, oz + wz))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
@ -360,9 +360,9 @@ impl Grippable for Wipeout {
|
|||
);
|
||||
vec![
|
||||
square_grip(0, Vec3::from(to_f32_3(corners[0]))),
|
||||
diamond_grip(1, Vec3::from(to_f32_3(corners[1]))),
|
||||
diamond_grip(2, Vec3::from(to_f32_3(corners[2]))),
|
||||
diamond_grip(3, Vec3::from(to_f32_3(corners[3]))),
|
||||
center_grip(1, Vec3::from(to_f32_3(corners[1]))),
|
||||
center_grip(2, Vec3::from(to_f32_3(corners[2]))),
|
||||
center_grip(3, Vec3::from(to_f32_3(corners[3]))),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use acadrust::entities::{Ray, XLine};
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, square_grip};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -48,7 +48,7 @@ impl Grippable for Ray {
|
|||
let guide_dist = 10.0_f64;
|
||||
vec![
|
||||
square_grip(0, Vec3::new(bp.x as f32, bp.y as f32, bp.z as f32)),
|
||||
diamond_grip(
|
||||
center_grip(
|
||||
1,
|
||||
Vec3::new(
|
||||
(bp.x + dir.x * guide_dist) as f32,
|
||||
|
|
@ -186,7 +186,7 @@ impl Grippable for XLine {
|
|||
let guide_dist = 10.0_f64;
|
||||
vec![
|
||||
square_grip(0, Vec3::new(bp.x as f32, bp.y as f32, bp.z as f32)),
|
||||
diamond_grip(
|
||||
center_grip(
|
||||
1,
|
||||
Vec3::new(
|
||||
(bp.x + dir.x * guide_dist) as f32,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
use acadrust::entities::{Body, Region, Solid3D};
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::entities::common::{diamond_grip, ro_prop as ro};
|
||||
use crate::entities::common::{center_grip, ro_prop as ro};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ fn acis_size_str(has_data: bool, sat_len: usize, sab_len: usize, is_binary: bool
|
|||
|
||||
impl Grippable for Solid3D {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
vec![diamond_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
|
|
@ -120,7 +120,7 @@ impl PropertyEditable for Solid3D {
|
|||
|
||||
impl Grippable for Region {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
vec![diamond_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
|
|
@ -188,7 +188,7 @@ impl PropertyEditable for Region {
|
|||
|
||||
impl Grippable for Body {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
vec![diamond_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
vec![center_grip(0, por_to_vec3(&self.point_of_reference))]
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use acadrust::entities::{Underlay, UnderlayDisplayFlags};
|
|||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{diamond_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
|
|
@ -87,7 +87,7 @@ impl Grippable for Underlay {
|
|||
if !self.clip_boundary_vertices.is_empty() {
|
||||
let world_verts = self.world_clip_boundary();
|
||||
for (i, v) in world_verts.iter().enumerate() {
|
||||
grips.push(diamond_grip(
|
||||
grips.push(center_grip(
|
||||
i + 1,
|
||||
Vec3::new(v.x as f32, v.y as f32, v.z as f32),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use glam::Vec3;
|
|||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{
|
||||
diamond_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
center_grip, edit_prop as edit, parse_f64, ro_prop as ro, square_grip,
|
||||
};
|
||||
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
|
|
@ -97,7 +97,7 @@ fn grips(vp: &Viewport) -> Vec<GripDef> {
|
|||
let hw = (vp.width / 2.0) as f32;
|
||||
let hh = (vp.height / 2.0) as f32;
|
||||
vec![
|
||||
diamond_grip(0, Vec3::new(cx, cy, cz)),
|
||||
center_grip(0, Vec3::new(cx, cy, cz)),
|
||||
square_grip(1, Vec3::new(cx + hw, cy + hh, cz)),
|
||||
square_grip(2, Vec3::new(cx - hw, cy + hh, cz)),
|
||||
square_grip(3, Vec3::new(cx - hw, cy - hh, cz)),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub fn grips_to_screen(
|
|||
grips: &[GripDef],
|
||||
view_proj: Mat4,
|
||||
bounds: Rectangle,
|
||||
) -> Vec<(usize, Point, bool, GripShape)> {
|
||||
) -> Vec<(usize, Point, bool, GripShape, Option<[f32; 2]>)> {
|
||||
grips
|
||||
.iter()
|
||||
.map(|g| {
|
||||
|
|
@ -45,7 +45,7 @@ pub fn grips_to_screen(
|
|||
bounds.x + (ndc.x + 1.0) * 0.5 * bounds.width,
|
||||
bounds.y + (1.0 - ndc.y) * 0.5 * bounds.height,
|
||||
);
|
||||
(g.id, screen, g.is_midpoint, g.shape)
|
||||
(g.id, screen, g.is_midpoint, g.shape, g.dir)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ pub fn grips_to_screen_paper(
|
|||
half_w: f32,
|
||||
half_h: f32,
|
||||
bounds: Rectangle,
|
||||
) -> Vec<(usize, Point, bool, GripShape)> {
|
||||
) -> Vec<(usize, Point, bool, GripShape, Option<[f32; 2]>)> {
|
||||
grips
|
||||
.iter()
|
||||
.map(|g| {
|
||||
|
|
@ -67,7 +67,7 @@ pub fn grips_to_screen_paper(
|
|||
(g.world.x - tx + half_w) / (2.0 * half_w) * bounds.width,
|
||||
(ty + half_h - g.world.y) / (2.0 * half_h) * bounds.height,
|
||||
);
|
||||
(g.id, screen, g.is_midpoint, g.shape)
|
||||
(g.id, screen, g.is_midpoint, g.shape, g.dir)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,23 +51,23 @@ pub struct PropSection {
|
|||
|
||||
// ── Grip types ─────────────────────────────────────────────────────────────
|
||||
|
||||
/// Visual marker shape for a grip point. The complete vocabulary used
|
||||
/// across entity types:
|
||||
/// * `Square` — vertex / endpoint that moves a single point.
|
||||
/// * `Rectangle` — direction-aware mid-segment stretch (polyline
|
||||
/// straight segments, dimension extension lines).
|
||||
/// * `Diamond` — midpoint of a curve / centre of a closed shape (drags
|
||||
/// the whole shape or stretches the midpoint).
|
||||
/// * `Triangle` — directional control (rotate / add vertex / continue).
|
||||
/// * `Circle` — parametric control (radius / dimension value).
|
||||
/// Visual marker shape for a grip point. The complete vocabulary that
|
||||
/// matches the standard CAD grip conventions:
|
||||
/// * `Square` — endpoint / vertex / centre. Drag → moves a single
|
||||
/// point or translates the entity.
|
||||
/// * `Rectangle` — direction-aware mid-segment stretch handle.
|
||||
/// Drawn as a small box rotated along `dir` (the in-plane segment
|
||||
/// direction in world XY). Used for polyline / wipeout / image /
|
||||
/// dimension segment midpoints.
|
||||
/// * `Triangle` — directional indicator (Phase 2: dynamic-block
|
||||
/// parameters, dimension reverse-arrow flips, multi-functional
|
||||
/// hover popups).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum GripShape {
|
||||
Square,
|
||||
Rectangle,
|
||||
Diamond,
|
||||
Triangle,
|
||||
Circle,
|
||||
}
|
||||
|
||||
/// Describes one grip point for an entity.
|
||||
|
|
@ -77,11 +77,15 @@ pub struct GripDef {
|
|||
pub id: usize,
|
||||
/// World-space position of the grip.
|
||||
pub world: Vec3,
|
||||
/// `true` → midpoint grip (diamond, translates whole object).
|
||||
/// `false` → endpoint grip (square, moves a single vertex).
|
||||
/// `true` → midpoint / centre grip (drags the whole shape).
|
||||
/// `false` → endpoint / vertex grip (moves a single point).
|
||||
pub is_midpoint: bool,
|
||||
/// Visual marker shape for the grip.
|
||||
pub shape: GripShape,
|
||||
/// World-XY direction vector used to orient a `Rectangle` grip
|
||||
/// along its segment. `None` for shapes that don't need rotation
|
||||
/// (Square, Triangle in non-directional contexts).
|
||||
pub dir: Option<[f32; 2]>,
|
||||
}
|
||||
|
||||
/// How to apply a grip drag result.
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ pub struct GripMarker {
|
|||
pub shape: GripShape,
|
||||
/// True → grip is currently being dragged (drawn filled red).
|
||||
pub is_hot: bool,
|
||||
/// World-XY direction vector — only consumed by the `Rectangle`
|
||||
/// shape to orient the box along its segment. `None` for grips
|
||||
/// that don't need rotation.
|
||||
pub dir: Option<[f32; 2]>,
|
||||
}
|
||||
|
||||
// ── Grid display params ───────────────────────────────────────────────────
|
||||
|
|
@ -424,29 +428,38 @@ impl canvas::Program<Message> for SelectionCanvas {
|
|||
Size::new(h * 2.0, h * 2.0),
|
||||
),
|
||||
GripShape::Rectangle => {
|
||||
// Wider-than-tall box — direction-aware mid-segment
|
||||
// stretch handle.
|
||||
let w = h * 1.4;
|
||||
let t = h * 0.7;
|
||||
canvas::Path::rectangle(
|
||||
Point::new(sp.x - w, sp.y - t),
|
||||
Size::new(w * 2.0, t * 2.0),
|
||||
)
|
||||
// Mid-segment stretch handle: small box, longer along
|
||||
// the segment direction so the affordance reads as
|
||||
// "stretch perpendicular to the segment". `dir` is a
|
||||
// world-XY direction vector; project it onto the
|
||||
// screen-X / screen-Y axes implied by the grip's
|
||||
// 2-D screen position to compute the in-plane angle.
|
||||
let half_long = h * 1.4;
|
||||
let half_short = h * 0.7;
|
||||
let (cos_t, sin_t) = match grip.dir {
|
||||
Some([dx, dy]) if (dx * dx + dy * dy) > 1e-12 => {
|
||||
let n = (dx * dx + dy * dy).sqrt();
|
||||
// Screen Y is inverted vs world Y → flip sin.
|
||||
(dx / n, -dy / n)
|
||||
}
|
||||
_ => (1.0, 0.0),
|
||||
};
|
||||
let ax = (cos_t * half_long, sin_t * half_long);
|
||||
let ay = (-sin_t * half_short, cos_t * half_short);
|
||||
canvas::Path::new(|b| {
|
||||
b.move_to(Point::new(sp.x + ax.0 + ay.0, sp.y + ax.1 + ay.1));
|
||||
b.line_to(Point::new(sp.x + ax.0 - ay.0, sp.y + ax.1 - ay.1));
|
||||
b.line_to(Point::new(sp.x - ax.0 - ay.0, sp.y - ax.1 - ay.1));
|
||||
b.line_to(Point::new(sp.x - ax.0 + ay.0, sp.y - ax.1 + ay.1));
|
||||
b.close();
|
||||
})
|
||||
}
|
||||
GripShape::Diamond => canvas::Path::new(|b| {
|
||||
b.move_to(Point::new(sp.x, sp.y - h));
|
||||
b.line_to(Point::new(sp.x + h, sp.y));
|
||||
b.line_to(Point::new(sp.x, sp.y + h));
|
||||
b.line_to(Point::new(sp.x - h, sp.y));
|
||||
b.close();
|
||||
}),
|
||||
GripShape::Triangle => canvas::Path::new(|b| {
|
||||
b.move_to(Point::new(sp.x, sp.y - h));
|
||||
b.line_to(Point::new(sp.x + h, sp.y + h));
|
||||
b.line_to(Point::new(sp.x - h, sp.y + h));
|
||||
b.close();
|
||||
}),
|
||||
GripShape::Circle => canvas::Path::circle(sp, h),
|
||||
};
|
||||
|
||||
if grip.is_hot {
|
||||
|
|
|
|||
Loading…
Reference in a new issue