Track mixed paper and viewport dimension references

This commit is contained in:
Christopher Overbeck committed 2026-09-15 16:00:11 -05:00
1 parent bd00d679a9
commit 5424a19482
28 files changed
+823 -846

No files matched your search

+62 -40
View File
@@ -9,9 +9,9 @@ use iced::time::Instant;
use iced::{Point, Rectangle};
use cadkernel::geom2d::Curve;
use acadrust::types::Handle;
use crate::command::{DimensionAssociationSource, TangentObject};
use acadrust::types::Handle;
use crate::scene::model::wire_model::{SnapHint, TangentGeom, WireModel};
use crate::scene::pick::interaction_index::WireSource;
const DEFAULT_OSNAP_RADIUS_PX: f32 = 15.0;
@@ -90,7 +90,7 @@ pub struct SnapResult {
/// Layout viewport this snap was seen *through*, when the snap ran against
/// model geometry displayed by a paper-space viewport. `None` for ordinary
/// model-space / paper-sheet snaps. Filled by the paper-space viewport snap
/// query, never by the engine itself. (PR1)
/// query, never by the engine itself.
pub viewport: Option<Handle>,
/// Owning entity of the geometry the snap landed on, when the engine could
/// attribute the feature to a wire.
@@ -102,7 +102,7 @@ pub struct SnapResult {
/// the instance path needs the document and the accepted model point, so
/// it happens once at accept time, in
/// `OpenCADStudio::resolve_source_block_path`, which fills
/// [`crate::scene::viewport_ref::SnapSourceRef::block_path`]. (PR1)
/// [`crate::scene::viewport_ref::SnapSourceRef::block_path`].
pub source: Option<DimensionAssociationSource>,
/// Second real object at an intersection, when both objects are known.
pub secondary_source: Option<DimensionAssociationSource>,
@@ -996,10 +996,10 @@ impl Snapper {
extension_base2: None,
extension_origin: None,
extension_dir: None,
viewport: None,
source: None,
secondary_source: None,
model_point: None,
viewport: None,
source: None,
secondary_source: None,
model_point: None,
})
}
@@ -1234,7 +1234,8 @@ impl Snapper {
let mut try_pt = |world: glam::DVec3,
snap_type: SnapType,
src: Option<DimensionAssociationSource>, secondary: Option<DimensionAssociationSource>| {
src: Option<DimensionAssociationSource>,
secondary: Option<DimensionAssociationSource>| {
let screen = world_to_screen(world, view_rot, eye, bounds);
if !in_bounds(screen) {
return;
@@ -1261,10 +1262,10 @@ impl Snapper {
extension_base2: None,
extension_origin: None,
extension_dir: None,
viewport: None,
source: src,
secondary_source: secondary,
model_point: None,
viewport: None,
source: src,
secondary_source: secondary,
model_point: None,
});
}
};
@@ -1318,7 +1319,9 @@ impl Snapper {
try_pt(
DVec3::from_array(point),
SnapType::Endpoint,
wire_source(wire), None);
wire_source(wire),
None,
);
}
// Tessellated open curves have no key-vertex set. Their only
// endpoints are first/last, so testing candidate wires remains
@@ -1338,7 +1341,9 @@ impl Snapper {
try_pt(
wp_f64(wire, wire.points.len() - 1),
SnapType::Endpoint,
wire_source(wire), None);
wire_source(wire),
None,
);
}
}
} else {
@@ -1361,7 +1366,9 @@ impl Snapper {
try_pt(
wp_f64(wire, wire.points.len() - 1),
SnapType::Endpoint,
src, None);
src,
None,
);
}
}
}
@@ -1412,7 +1419,9 @@ impl Snapper {
try_pt(
nearest_on_segment(cursor_world, seg.a, seg.b),
SnapType::Nearest,
wires.source_wire(seg.wire).and_then(wire_source), None);
wires.source_wire(seg.wire).and_then(wire_source),
None,
);
}
} else {
for wire in in_range_wires.iter() {
@@ -1436,7 +1445,9 @@ impl Snapper {
try_pt(
foot,
SnapType::Perpendicular,
wires.source_wire(seg.wire).and_then(wire_source), None);
wires.source_wire(seg.wire).and_then(wire_source),
None,
);
}
}
} else {
@@ -1465,7 +1476,9 @@ impl Snapper {
try_pt(
point,
SnapType::Intersection,
wires.source_wire(segment.wire).and_then(wire_source), None);
wires.source_wire(segment.wire).and_then(wire_source),
None,
);
}
}
}
@@ -1563,7 +1576,7 @@ impl Snapper {
for &wire_j in &local_wires[idx + 1..] {
if let Some(points) = exact_curve_intersections(wire_i, wire_j) {
// Two wires meet here; attribute the feature to the
// first. PR3 resolves the true pair.
// first. Association resolution requires both source paths.
let src = wire_source(wire_i);
for point in points {
try_pt(point, SnapType::Intersection, src, wire_source(wire_j));
@@ -1600,7 +1613,9 @@ impl Snapper {
try_pt(
pt,
SnapType::Intersection,
wires.source_wire(a.wire).and_then(wire_source), wires.source_wire(b.wire).and_then(wire_source));
wires.source_wire(a.wire).and_then(wire_source),
wires.source_wire(b.wire).and_then(wire_source),
);
if exact_cursor {
break 'intersection_sweep;
}
@@ -1646,7 +1661,12 @@ impl Snapper {
continue;
}
if let Some(pt) = seg_intersect_3d(a0, a1, b0, b1) {
try_pt(pt, SnapType::Intersection, wire_source(wire_i), wire_source(wire_j));
try_pt(
pt,
SnapType::Intersection,
wire_source(wire_i),
wire_source(wire_j),
);
}
}
}
@@ -1772,7 +1792,9 @@ impl Snapper {
try_pt(
segment_a.a + ta as f64 * (segment_a.b - segment_a.a),
SnapType::ApparentIntersection,
wires.source_wire(segment_a.wire).and_then(wire_source), wires.source_wire(segment_b.wire).and_then(wire_source));
wires.source_wire(segment_a.wire).and_then(wire_source),
wires.source_wire(segment_b.wire).and_then(wire_source),
);
if dist2(apparent_screen, cursor_screen) <= f32::EPSILON {
break 'apparent_sweep;
}
@@ -1812,7 +1834,9 @@ impl Snapper {
try_pt(
wa0 + ta as f64 * (wa1 - wa0),
SnapType::ApparentIntersection,
wire_source(wire_i), wire_source(wire_j));
wire_source(wire_i),
wire_source(wire_j),
);
}
}
}
@@ -2033,10 +2057,10 @@ impl Snapper {
extension_base2: None,
extension_origin: None,
extension_dir: None,
viewport: None,
source: wire_source(wire),
secondary_source: None,
model_point: None,
viewport: None,
source: wire_source(wire),
secondary_source: None,
model_point: None,
});
}
}
@@ -2206,19 +2230,13 @@ fn snap_tier(t: SnapType) -> u8 {
}
}
/// Candidate ordering: tier first, then cursor distance, and only when two
/// candidates are effectively equidistant (coincident features) the classic
/// sub-priority — so an Endpoint still beats an Intersection sitting on the
/// exact same point. Distances are screen-px²; 4.0 ≈ a 2 px coincidence band.
/// Choose between two independently-computed snap results using the SAME
/// priority-then-distance rule the engine applies internally
/// ([`snap_better`]): a higher-priority snap inside the aperture wins even
/// when a lower-priority one is nearer, and distance only breaks ties.
///
/// Used to merge the ordinary paper-sheet snap with the viewport snap taken
/// through a layout viewport (PR1). Both `screen` fields must be expressed in
/// the same pixel frame.
pub fn merge_snap(a: Option<SnapResult>, b: Option<SnapResult>, cursor: Point) -> Option<SnapResult> {
/// Merge paper and viewport snaps using the engine's ordering.
/// Both screen positions must use canvas pixels.
pub fn merge_snap(
a: Option<SnapResult>,
b: Option<SnapResult>,
cursor: Point,
) -> Option<SnapResult> {
match (a, b) {
(Some(a), Some(b)) => {
let (at, asub) = (snap_tier(a.snap_type), snap_priority(a.snap_type));
@@ -2235,6 +2253,10 @@ pub fn merge_snap(a: Option<SnapResult>, b: Option<SnapResult>, cursor: Point) -
}
}
/// Candidate ordering: tier first, then cursor distance, and only when two
/// candidates are effectively equidistant (coincident features) the classic
/// sub-priority — so an Endpoint still beats an Intersection sitting on the
/// exact same point. Distances are screen-px²; 4.0 ≈ a 2 px coincidence band.
fn snap_better(tier: u8, d2: f32, sub: u8, best: (u8, f32, u8)) -> bool {
let (bt, bd2, bsub) = best;
if tier != bt {