forked from 1b-agent/opencadstudio-upstream
feat(snap): snap paper space through layout viewports
Paper-space commands could not pick model geometry displayed by a layout viewport: `hit_test_wires` deliberately excludes viewport content in PSPACE, and that has to stay true for selection. This adds the capability as a separate query rather than by changing those semantics. `OpenCADStudio::paper_viewport_snap` walks the current layout's displayed content viewports top-most first, skips those whose display is off or whose clip boundary excludes the cursor, and for each one runs the EXISTING snap engine against that viewport's model wires using the viewport's own camera and screen rectangle (`viewport_edit_frame_for`). The accepted model point is mapped back onto the sheet with `ViewportFrame::model_to_paper`, so everything downstream keeps working in paper coordinates — LINE still draws at the projected paper point. Grid snap and the paper-space rubber-band origin are suppressed for that pass; results outside the clip are rejected so no snap lands on a clip cut. Locked viewports snap like unlocked ones. No projected geometry is rebuilt per pointer event: the wire set is the renderer's resident per-viewport Arc (`model_wires_for_viewport_arc`) and the broad phase reuses the shared interaction index over it. The result is merged with the ordinary paper-sheet snap by `snap::merge_snap`, which applies the engine's own priority-then-distance rule, in both the cursor-move and the click paths. `SnapResult` gains Copy fields `viewport: Option<Handle>` and `source: Option<DimensionAssociationSource>`; the engine fills `source` from the owning wire wherever it knows it. `AcceptedSnap::from_snap` converts a result plus frame into the shared PR2/PR3 contract type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
53576fabdf
commit
6def465ba5
5 files changed
+525
-33
No files matched your search
@@ -148,6 +148,60 @@ impl AcceptedSnap {
|
||||
pub fn through_viewport(&self) -> bool {
|
||||
self.viewport.is_some()
|
||||
}
|
||||
|
||||
/// Build an accepted snap from a [`crate::snap::SnapResult`].
|
||||
///
|
||||
/// * `frame` `Some`: the snap ran against MODEL geometry through that
|
||||
/// viewport, so `snap.world` is a model point and the paper point is its
|
||||
/// projection onto the sheet.
|
||||
/// * `frame` `None`: the snap ran in the current space directly, so paper
|
||||
/// and model point coincide.
|
||||
///
|
||||
/// PR1 fills `source` from `SnapResult::source` (the owning entity handle
|
||||
/// the snap engine attributed the feature to); `block_path` stays empty
|
||||
/// until PR3 resolves block instance chains.
|
||||
pub fn from_snap(snap: &crate::snap::SnapResult, frame: Option<ViewportFrame>) -> Self {
|
||||
let (paper_point, model_point) = match &frame {
|
||||
Some(frame) => (frame.model_to_paper(snap.world), snap.world),
|
||||
None => (snap.world, snap.world),
|
||||
};
|
||||
Self {
|
||||
paper_point,
|
||||
model_point,
|
||||
viewport: frame.map(|f| f.viewport),
|
||||
frame,
|
||||
source: snap.source.map(|source| SnapSourceRef {
|
||||
source,
|
||||
block_path: Vec::new(),
|
||||
snap_type: snap.snap_type,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// The paper point with the viewport context of `self`, but no geometry
|
||||
/// identity — used when a command constrains a snapped point (ortho/polar,
|
||||
/// axis lock) so the recorded point matches what was committed.
|
||||
pub fn with_paper_point(mut self, paper_point: DVec3) -> Self {
|
||||
// Compare in the sheet plane only: paper space is flat and callers
|
||||
// clamp `z` to 0, which must not be mistaken for the point having been
|
||||
// moved off the snapped feature (and must not discard the snapped
|
||||
// model point's real elevation).
|
||||
let moved = self
|
||||
.paper_point
|
||||
.truncate()
|
||||
.distance_squared(paper_point.truncate())
|
||||
> 1e-18;
|
||||
if moved {
|
||||
// The committed point is no longer the feature that was snapped.
|
||||
self.source = None;
|
||||
self.model_point = match &self.frame {
|
||||
Some(frame) => frame.paper_to_model(paper_point),
|
||||
None => paper_point,
|
||||
};
|
||||
}
|
||||
self.paper_point = paper_point;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The two independent factors that turn a raw measured distance into the
|
||||
|
||||
Reference in new issue
Block a user