mirror of
https://github.com/aphrody-labs/cadcraft.git
synced 2026-10-13 19:26:54 +00:00
Performance: reuse nearby snap geometry for tracking acquisition (#698)
Squash of PR #698 by @cyfung1031. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4bb3acb913
commit
890f7f1c6f
3 files changed
+140
-12
No files matched your search
@@ -154,13 +154,31 @@ pub(crate) fn osnap_with(
|
||||
base: Option<Vec2>,
|
||||
deferred: bool,
|
||||
) -> Option<SnapHit> {
|
||||
osnap_with_neighborhood(d, space, ix, cursor, aperture, osmode, base, deferred).0
|
||||
}
|
||||
|
||||
/// Find the best object snap and retain the local-space primitives gathered for that query.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn osnap_with_neighborhood(
|
||||
d: &Drawing,
|
||||
space: &Space,
|
||||
ix: &Option<std::sync::Arc<crate::spatial::SpatialIndex>>,
|
||||
cursor: Vec2,
|
||||
aperture: f64,
|
||||
osmode: u32,
|
||||
base: Option<Vec2>,
|
||||
deferred: bool,
|
||||
) -> (Option<SnapHit>, Option<Vec<Prim>>) {
|
||||
if osmode == 0 || osmode & mode::OFF != 0 || !cursor.is_finite() || !(aperture.is_finite() && aperture > 0.0) {
|
||||
return None;
|
||||
return (None, None);
|
||||
}
|
||||
let mut best = osnap_in(d, space, ix, cursor, aperture, osmode, base, deferred, &[], None);
|
||||
let (mut best, near_prims) = match osnap_in_with_prims(d, space, ix, cursor, aperture, osmode, base, deferred, &[], None) {
|
||||
Some((hit, prims)) => (hit, Some(prims)),
|
||||
None => (None, None),
|
||||
};
|
||||
let views = viewport::near(d, space, cursor, aperture);
|
||||
if views.is_empty() {
|
||||
return best;
|
||||
return (best, near_prims);
|
||||
}
|
||||
let model = Space::Model;
|
||||
let model_ix = crate::spatial::index(d, &model);
|
||||
@@ -190,7 +208,7 @@ pub(crate) fn osnap_with(
|
||||
best = Some(hit);
|
||||
}
|
||||
}
|
||||
best
|
||||
(best, near_prims)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -206,6 +224,22 @@ fn osnap_in(
|
||||
frozen: &[String],
|
||||
clip: Option<Bounds2>,
|
||||
) -> Option<SnapHit> {
|
||||
osnap_in_with_prims(d, space, ix, cursor, aperture, osmode, base, deferred, frozen, clip).and_then(|(hit, _)| hit)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn osnap_in_with_prims(
|
||||
d: &Drawing,
|
||||
space: &Space,
|
||||
ix: &Option<std::sync::Arc<crate::spatial::SpatialIndex>>,
|
||||
cursor: Vec2,
|
||||
aperture: f64,
|
||||
osmode: u32,
|
||||
base: Option<Vec2>,
|
||||
deferred: bool,
|
||||
frozen: &[String],
|
||||
clip: Option<Bounds2>,
|
||||
) -> Option<(Option<SnapHit>, Vec<Prim>)> {
|
||||
let (mut cands, near_prims) = gather_in(d, space, ix, cursor, aperture, osmode, frozen)?;
|
||||
// Intersections between nearby primitives; Apparent Intersection also where their extensions
|
||||
// meet (lines extended, arcs as full circles: in 2D, the "apparent" crossing of objects
|
||||
@@ -236,7 +270,8 @@ fn osnap_in(
|
||||
}
|
||||
}
|
||||
}
|
||||
finish(cands, &near_prims, cursor, aperture, osmode, base, deferred, clip)
|
||||
let hit = finish(cands, &near_prims, cursor, aperture, osmode, base, deferred, clip);
|
||||
Some((hit, near_prims))
|
||||
}
|
||||
|
||||
/// Whether a primitive has a meaningful extension (a line, an arc, a circle, an xline or ray).
|
||||
|
||||
@@ -587,14 +587,42 @@ pub fn candidate(
|
||||
cfg: &TrackConfig,
|
||||
base: Option<Vec2>,
|
||||
) -> Option<Acquired> {
|
||||
let nothing_to_acquire = !cfg.otrack && !cfg.running(mode::EXT | mode::PAR | mode::APP);
|
||||
if nothing_to_acquire || !(cursor.is_finite() && aperture.is_finite() && aperture > 0.0) {
|
||||
if !can_acquire(cursor, aperture, cfg) {
|
||||
return None;
|
||||
}
|
||||
let ix = crate::spatial::index(d, space);
|
||||
let prims = super::gather(d, space, &ix, cursor, aperture, mode::NEA).map(|(_, p)| p).unwrap_or_default();
|
||||
candidate_from_prims(cursor, hit, aperture, cfg, base, &prims)
|
||||
}
|
||||
|
||||
pub(crate) fn candidate_with_prims(
|
||||
cursor: Vec2,
|
||||
hit: Option<&SnapHit>,
|
||||
aperture: f64,
|
||||
cfg: &TrackConfig,
|
||||
base: Option<Vec2>,
|
||||
prims: &[Prim],
|
||||
) -> Option<Acquired> {
|
||||
if !can_acquire(cursor, aperture, cfg) {
|
||||
return None;
|
||||
}
|
||||
candidate_from_prims(cursor, hit, aperture, cfg, base, prims)
|
||||
}
|
||||
|
||||
fn can_acquire(cursor: Vec2, aperture: f64, cfg: &TrackConfig) -> bool {
|
||||
(cfg.otrack || cfg.running(mode::EXT | mode::PAR | mode::APP)) && cursor.is_finite() && aperture.is_finite() && aperture > 0.0
|
||||
}
|
||||
|
||||
fn candidate_from_prims(
|
||||
cursor: Vec2,
|
||||
hit: Option<&SnapHit>,
|
||||
aperture: f64,
|
||||
cfg: &TrackConfig,
|
||||
base: Option<Vec2>,
|
||||
prims: &[Prim],
|
||||
) -> Option<Acquired> {
|
||||
let hit = hit.filter(|h| h.deferred.is_none() && h.mode != mode::NEA && h.point.is_finite());
|
||||
let ext_end = if cfg.running(mode::EXT) { extension_near(&prims, hit.map_or(cursor, |h| h.point), aperture) } else { None };
|
||||
let ext_end = if cfg.running(mode::EXT) { extension_near(prims, hit.map_or(cursor, |h| h.point), aperture) } else { None };
|
||||
if let Some((at, ext)) = ext_end
|
||||
&& hit.is_none_or(|h| h.point.dist(at) <= cadcraft_geom::EPS * (1.0 + at.len()))
|
||||
{
|
||||
@@ -761,8 +789,12 @@ impl crate::Session {
|
||||
let last = self.last_segment.filter(|(_, b)| q.base.is_some_and(|base| base.near(*b, 1e-9 * (1.0 + b.len())))).map(|(a, b)| a.angle_to(b));
|
||||
let cfg = TrackConfig::relative_to(&s, angbase, last);
|
||||
let osmode = s.osmode | if s.osnaphatch { mode::HATCH } else { 0 };
|
||||
let hit = super::osnap(&doc, &space, raw, q.aperture, osmode, q.base, q.deferred);
|
||||
let cand = candidate(&doc, &space, raw, hit.as_ref(), q.aperture, &cfg, q.base);
|
||||
let ix = crate::spatial::index(&doc, &space);
|
||||
let (hit, near_prims) = super::osnap_with_neighborhood(&doc, &space, &ix, raw, q.aperture, osmode, q.base, q.deferred);
|
||||
let cand = match (osmode & mode::HATCH, near_prims.as_deref()) {
|
||||
(0, Some(prims)) => candidate_with_prims(raw, hit.as_ref(), q.aperture, &cfg, q.base, prims),
|
||||
_ => candidate(&doc, &space, raw, hit.as_ref(), q.aperture, &cfg, q.base),
|
||||
};
|
||||
let wait = self.tracking.hover(cand, q.now, cfg.shift_acquire, q.shift);
|
||||
if let Some(h) = hit {
|
||||
self.tracking.last = None;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Object snap tracking (OTRACK, F11) and the Extension, Parallel and Apparent Intersection
|
||||
//! snaps, driven through `Session::snap_cursor` as the UI does, without the UI.
|
||||
|
||||
use cadcraft_doc::EntityKind;
|
||||
use cadcraft_doc::{Common, EntityKind, Insert, Line, Space};
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::geom::Vec2;
|
||||
use cadcraft_engine::geom::{Vec2, Vec3};
|
||||
use cadcraft_engine::snap::tracking::{Acquired, CursorQuery, CursorSnap, DWELL, MAX_ACQUIRED, PathKind, polar_angles};
|
||||
use cadcraft_engine::snap::{self, mode};
|
||||
use serde_json::json;
|
||||
@@ -25,6 +25,28 @@ fn line(s: &mut Session, a: (f64, f64), b: (f64, f64)) {
|
||||
s.execute("line", &json!({ "points": [[a.0, a.1], [b.0, b.1]] })).unwrap();
|
||||
}
|
||||
|
||||
fn insert_block(s: &mut Session, name: &str, at: Vec2) -> String {
|
||||
s.doc_mut()
|
||||
.unwrap()
|
||||
.add(
|
||||
&Space::Model,
|
||||
Common::default(),
|
||||
EntityKind::Insert(Insert {
|
||||
block: name.into(),
|
||||
insert: Vec3::new(at.x, at.y, 0.0),
|
||||
scale: Vec3::new(1.0, 1.0, 1.0),
|
||||
rotation: 0.0,
|
||||
attribs: Vec::new(),
|
||||
cols: 1,
|
||||
rows: 1,
|
||||
col_spacing: 0.0,
|
||||
row_spacing: 0.0,
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
.hex()
|
||||
}
|
||||
|
||||
fn setvar(s: &mut Session, name: &str, value: serde_json::Value) {
|
||||
s.execute("setvar", &json!({ "name": name, "value": value })).unwrap();
|
||||
}
|
||||
@@ -37,6 +59,45 @@ fn at(s: &mut Session, cursor: Vec2, base: Option<Vec2>) -> CursorSnap {
|
||||
s.snap_cursor(cursor, &query(base, 100.0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_cursor_tracking_acquires_nested_block_geometry() {
|
||||
let mut s = session(mode::END | mode::CEN | mode::INT | mode::EXT);
|
||||
assert!(s.settings.otrack);
|
||||
let line = s
|
||||
.doc_mut()
|
||||
.unwrap()
|
||||
.add(&Space::Model, Common::default(), EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 0.0), b: Vec3::new(10.0, 0.0, 0.0) }))
|
||||
.unwrap()
|
||||
.hex();
|
||||
s.execute("block", &json!({ "name": "INNER", "base": [0, 0], "handles": [line], "keep": "delete" })).unwrap();
|
||||
let inner = insert_block(&mut s, "INNER", Vec2::ZERO);
|
||||
s.execute("block", &json!({ "name": "OUTER", "base": [0, 0], "handles": [inner], "keep": "delete" })).unwrap();
|
||||
insert_block(&mut s, "OUTER", Vec2::ZERO);
|
||||
|
||||
rest(&mut s, v(10.02, 0.02), None);
|
||||
|
||||
assert_eq!(s.tracking.acquired.len(), 1);
|
||||
assert!(matches!(
|
||||
s.tracking.acquired.first(),
|
||||
Some(Acquired::Point { at, name, ext: Some(_) }) if at.near(v(10.0, 0.0), 1e-9) && *name == "Endpoint"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osnaphatch_geometry_is_not_used_for_tracking_extension() {
|
||||
let mut s = session(mode::END | mode::EXT);
|
||||
s.execute("rectang", &json!({ "p1": [0, 0], "p2": [10, 5] })).unwrap();
|
||||
let boundary = s.doc().unwrap().model.iter().last().unwrap().handle.hex();
|
||||
s.execute("hatch", &json!({ "handles": [boundary] })).unwrap();
|
||||
s.execute("erase", &json!({ "handles": [boundary] })).unwrap();
|
||||
setvar(&mut s, "OSNAPHATCH", json!(1));
|
||||
|
||||
rest(&mut s, v(10.02, 4.98), None);
|
||||
|
||||
assert_eq!(s.tracking.acquired.len(), 1);
|
||||
assert!(matches!(s.tracking.acquired.first(), Some(Acquired::Point { ext: None, .. })));
|
||||
}
|
||||
|
||||
/// Rest the cursor at `p` long enough to acquire (or release) what is there, then move off.
|
||||
fn rest(s: &mut Session, p: Vec2, base: Option<Vec2>) {
|
||||
s.snap_cursor(p, &query(base, 0.0));
|
||||
|
||||
Reference in new issue
Block a user