forked from 1b-agent/opencadstudio-upstream
feat(pick): select frame-like entities by clicking inside them
Images, OLE frames, wipeouts, underlays, paper-space viewports and
drawing-view borders were selectable only on their outline. Each now
carries an interior pick surface (pick_tris over its rectangle, or the
triangulated clip polygon for a clipped underlay), ranked below edge
and fill hits so content drawn inside still wins the click. The
layout's own sheet ("overall") viewport is excluded — identified by
the Layout object's viewport link, since DWG files carry viewport
id = 0 and some center the sheet viewport off-origin — or it would
swallow every click over the real viewports beneath it. Drawing-view
borders draw nothing normally; they emit an invisible pick-only wire
whose rect outline appears while selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
84adb71cee
commit
e1a4ddef5e
6 files changed
+116
-10
No files matched your search
@@ -119,6 +119,13 @@ pub fn format_angle(value_rad: f64) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Two interior triangles covering a quad (flat list, 6 vertices) — the
|
||||
/// click-anywhere pick surface for frame-like entities (image, OLE frame,
|
||||
/// underlay, wipeout). Corners in ring order.
|
||||
pub fn quad_pick_tris(c: &[[f64; 3]; 4]) -> Vec<[f64; 3]> {
|
||||
vec![c[0], c[1], c[2], c[0], c[2], c[3]]
|
||||
}
|
||||
|
||||
pub fn square_grip(id: usize, world: glam::DVec3) -> GripDef {
|
||||
GripDef {
|
||||
id,
|
||||
|
||||
@@ -43,7 +43,14 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
|
||||
];
|
||||
let center = glam::DVec3::new(cx, cy, z);
|
||||
TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
// Interior pick surface: the frame selects on a click anywhere
|
||||
// inside, not just on its border.
|
||||
pick_tris: crate::entities::common::quad_pick_tris(&[
|
||||
[x0, y0, z],
|
||||
[x1, y0, z],
|
||||
[x1, y1, z],
|
||||
[x0, y1, z],
|
||||
]),
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts: vec![(center, SnapHint::Center)],
|
||||
tangent_geoms: vec![],
|
||||
|
||||
@@ -136,7 +136,9 @@ impl TruckConvertible for RasterImage {
|
||||
path.is_empty() || crate::scene::model::image_model::resolve_image(path).is_some();
|
||||
if resolvable {
|
||||
return Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
// Interior pick surface: the image selects on a click anywhere
|
||||
// inside its frame, not just on the border.
|
||||
pick_tris: crate::entities::common::quad_pick_tris(&corners),
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
@@ -223,7 +225,7 @@ impl TruckConvertible for RasterImage {
|
||||
}
|
||||
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris: crate::entities::common::quad_pick_tris(&corners),
|
||||
object: TruckObject::Text(groups),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
@@ -457,7 +459,9 @@ impl TruckConvertible for Wipeout {
|
||||
};
|
||||
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
// Interior pick surface — a wipeout reads as a solid patch, so a
|
||||
// click anywhere on it should select it.
|
||||
pick_tris: crate::entities::common::quad_pick_tris(&corners),
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
|
||||
@@ -52,8 +52,12 @@ impl TruckConvertible for Underlay {
|
||||
}
|
||||
// Insertion grip.
|
||||
let key: Vec<[f64; 3]> = pts.clone();
|
||||
// Interior pick surface over the clip polygon so the underlay
|
||||
// selects on a click anywhere inside, not just on its outline.
|
||||
let ring: Vec<[f64; 3]> = world_verts.iter().map(|v| [v.x, v.y, v.z]).collect();
|
||||
let pick_tris = crate::entities::mesh::triangulate_planar(&ring);
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris,
|
||||
object: TruckObject::Lines(pts),
|
||||
snap_pts: vec![(glam::DVec3::new(self.insertion_point.x, self.insertion_point.y, self.insertion_point.z), SnapHint::Node)],
|
||||
tangent_geoms: vec![],
|
||||
|
||||
@@ -624,9 +624,64 @@ pub(crate) fn tessellate_entity(
|
||||
line_weight_px,
|
||||
);
|
||||
}
|
||||
// Drawing-view borders are non-plotting aids: nothing is drawn.
|
||||
if let EntityType::ViewBorder(_) = e {
|
||||
return vec![];
|
||||
// Drawing-view borders are non-plotting aids: nothing is drawn normally,
|
||||
// but the view must still be click-selectable anywhere inside its
|
||||
// rectangle — emit an invisible pick-only wire carrying the border rect as
|
||||
// its interior pick surface. When selected, the rect outline is drawn so
|
||||
// the selection has visible feedback.
|
||||
if let EntityType::ViewBorder(b) = e {
|
||||
let (x0, y0) = (b.min[0], b.min[1]);
|
||||
let (x1, y1) = (b.max[0], b.max[1]);
|
||||
if !(x1 > x0 && y1 > y0) {
|
||||
return vec![];
|
||||
}
|
||||
let (pick_tris, pick_tris_low) =
|
||||
super::tessellate::points_to_ds(crate::entities::common::quad_pick_tris(&[
|
||||
[x0, y0, 0.0],
|
||||
[x1, y0, 0.0],
|
||||
[x1, y1, 0.0],
|
||||
[x0, y1, 0.0],
|
||||
]));
|
||||
let points: Vec<[f32; 3]> = if sel {
|
||||
vec![
|
||||
[x0 as f32, y0 as f32, 0.0],
|
||||
[x1 as f32, y0 as f32, 0.0],
|
||||
[x1 as f32, y1 as f32, 0.0],
|
||||
[x0 as f32, y1 as f32, 0.0],
|
||||
[x0 as f32, y0 as f32, 0.0],
|
||||
]
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
return vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
fill_is_3d: false,
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: h.value().to_string(),
|
||||
points,
|
||||
points_low: Vec::new(),
|
||||
color: WireModel::SELECTED,
|
||||
selected: sel,
|
||||
aci: 0,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px: 1.0,
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![
|
||||
[x0, y0, 0.0],
|
||||
[x1, y1, 0.0],
|
||||
],
|
||||
aabb: [x0 as f32, y0 as f32, x1 as f32, y1 as f32],
|
||||
plinegen: true,
|
||||
fill_tris: vec![],
|
||||
fill_tris_low: Vec::new(),
|
||||
}];
|
||||
}
|
||||
|
||||
// ── Dimension baked-block fast path ─────────────────────────────────────
|
||||
|
||||
@@ -1392,12 +1392,41 @@ pub fn tessellate(
|
||||
// f64 for the WireModel's double-single-era snap buffer.
|
||||
let snap_pts: Vec<(glam::DVec3, SnapHint)> =
|
||||
snap_pts.into_iter().map(|(p, h)| (p.as_dvec3(), h)).collect();
|
||||
// A paper-space viewport is a window, not a wireframe: give it an interior
|
||||
// pick surface so a click anywhere inside the frame selects it. Ranked
|
||||
// below edge and fill hits, so content drawn inside still wins the click.
|
||||
// The sheet ("overall") viewport is the layout's own invisible camera
|
||||
// frame covering the whole page — never pickable, or it would swallow
|
||||
// every click over the real viewports beneath it. It is identified by the
|
||||
// Layout object's viewport link (authoritative — DWG files carry id = 0
|
||||
// and this file class centres the sheet viewport off-origin, so neither
|
||||
// the id nor the geometry heuristic alone is reliable), with
|
||||
// `is_content_viewport` as the fallback classifier.
|
||||
let is_sheet_vp = |vp: &acadrust::entities::Viewport| {
|
||||
let h = vp.common.handle;
|
||||
document.objects.values().any(|obj| {
|
||||
matches!(obj, acadrust::objects::ObjectType::Layout(l) if l.viewport == h)
|
||||
}) || !crate::scene::Scene::is_content_viewport(vp)
|
||||
};
|
||||
let (pick_tris, pick_tris_low) = match entity {
|
||||
EntityType::Viewport(vp) if !is_sheet_vp(vp) => {
|
||||
let (cx, cy, cz) = (vp.center.x, vp.center.y, vp.center.z);
|
||||
let (hw, hh) = (vp.width / 2.0, vp.height / 2.0);
|
||||
points_to_ds(crate::entities::common::quad_pick_tris(&[
|
||||
[cx - hw, cy - hh, cz],
|
||||
[cx + hw, cy - hh, cz],
|
||||
[cx + hw, cy + hh, cz],
|
||||
[cx - hw, cy + hh, cz],
|
||||
]))
|
||||
}
|
||||
_ => (Vec::new(), Vec::new()),
|
||||
};
|
||||
vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
fill_is_3d: false,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
pick_tris,
|
||||
pick_tris_low,
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
|
||||
Reference in new issue
Block a user