fix(pdf): per-piece snap bounds, linear image layering, snap op checks

Review of #1484:
- The underlay's snap geometry became one hidden wire per segment, but
  every piece took the whole page's bounding box, so each cursor move
  visited every piece. Each piece now gets its own box.
- Assigning unlayered images to the last layer compared Debug strings of
  every layered path and text per drawn item (quadratic, formatted anew
  each time); the owners are keyed once in a map.
- The `snap` automation op used (0, 0, 0) for a missing point; it now
  answers invalid_point. It also left the live snapper's base point set
  to the query's; the base point is restored.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-09-25 15:17:53 +03:00
1 parent fc48087e50
commit 7e90d4d1a1
4 files changed
+23 -15

No files matched your search

+5 -6
View File
@@ -708,13 +708,12 @@ impl OpenCADStudio {
"measure" => self.control_measure(&req),
// The object snap a cursor over `point` (world) would get;
// `from` is the base for perpendicular and tangent.
"snap" => {
"snap" => 'snap: {
let p = &req["point"];
let world = glam::DVec3::new(
p[0].as_f64().unwrap_or(0.0),
p[1].as_f64().unwrap_or(0.0),
p[2].as_f64().unwrap_or(0.0),
);
let (Some(x), Some(y)) = (p[0].as_f64(), p[1].as_f64()) else {
break 'snap failure("invalid_point", "point must be [x, y] or [x, y, z]");
};
let world = glam::DVec3::new(x, y, p[2].as_f64().unwrap_or(0.0));
let from = req["from"].as_array().map(|f| {
glam::DVec3::new(
f.first().and_then(|v| v.as_f64()).unwrap_or(0.0),
+7 -3
View File
@@ -2908,11 +2908,15 @@ impl OpenCADStudio {
self.snapper.osnap_radius_px,
);
// Perpendicular and tangent measure from `from`, else the running
// command's last point.
// command's last point; the live cursor's base point is restored.
let live_from = self.snapper.from_point;
self.snapper.from_point = from.or(self.last_point).map(|p| p.as_vec3());
let (go, gr) = self.drafting_grid_basis(i);
self.snapper
.snap(raw, p, &candidates, view_rot, eye, bounds, go, gr, None)
let hit = self
.snapper
.snap(raw, p, &candidates, view_rot, eye, bounds, go, gr, None);
self.snapper.from_point = live_from;
hit
}
/// Apply one frame of a UCS-icon grip drag: map the cursor onto the active
+3 -1
View File
@@ -1308,7 +1308,9 @@ fn tessellate_entity_inner(
crate::scene::model::wire_model::SnapHint::Center,
)];
}
set_wire_aabb(&mut wire, aabb);
// Each piece gets its own box (not the page's), so candidate
// gathering only visits the pieces near the cursor.
set_wire_aabb(&mut wire, WireModel::UNBOUNDED_AABB);
bases.push(wire);
}
}
+8 -5
View File
@@ -200,11 +200,14 @@ fn images_to_last_layer(
return;
};
let key = |s: &dyn std::fmt::Debug| format!("{s:?}");
let owner = |item: String| {
layered.iter().position(|(_, pv)| {
pv.paths.iter().any(|p| key(p) == item) || pv.texts.iter().any(|t| key(t) == item)
})
};
// Each layered path and text keyed once; the first layer holding it wins.
let mut owners: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for (n, (_, pv)) in layered.iter().enumerate() {
for item in pv.paths.iter().map(|p| key(p)).chain(pv.texts.iter().map(|t| key(t))) {
owners.entry(item).or_insert(n);
}
}
let owner = |item: String| owners.get(&item).copied();
let mut last: Option<usize> = None;
let mut moves: Vec<(usize, PdfImage)> = Vec::new();
for drawn in &full.order {