Offset of an irregular closed spline no longer fails its error certificate; offset fit nodes stay private (#1009)
Fixes #971. At the node limit the offset certificate splits the fitted curve where it meets the exact offset midpoint, so curves with different parameter speeds certify within the same 0.002 mm tolerance. Associated offset fit nodes are hidden from display, picking, box selection and snapping until Break Association. Thanks @Karl-VonRichter.
This commit is contained in:
1 parent
2f91338d3d
commit
9ca7d307a4
9 files changed
+319
-41
No files matched your search
@@ -905,7 +905,7 @@ pub(crate) fn snaps_to_points(accept: Accept) -> bool {
|
||||
}
|
||||
|
||||
/// Which of a sketch's points a face place snaps to (the Single hole's): the points of geometry
|
||||
/// the sketch shows. Not a text outline's points, which are never drawn or picked on their own
|
||||
/// the sketch shows. Not generated offset nodes or a text outline's points, which are never drawn or picked on their own
|
||||
/// (as `view::sketch_point_visible` has it), nor projected points while the sketch hides
|
||||
/// projected geometry (Show Projected off), whose curves aren't picked either. Hidden sketches
|
||||
/// offer no points at all. Show Points doesn't count: it only hides the markers of the sketch
|
||||
@@ -913,35 +913,17 @@ pub(crate) fn snaps_to_points(accept: Accept) -> bool {
|
||||
pub(crate) struct SnapPoints<'a> {
|
||||
/// Show Projected is off: no linked point is taken.
|
||||
hide_linked: bool,
|
||||
/// The text sources' link ids (looked up once, however many points and links there are).
|
||||
text: std::collections::HashSet<&'a str>,
|
||||
shown: solidcraft_engine::view::SketchPointVisibility<'a>,
|
||||
}
|
||||
|
||||
impl<'a> SnapPoints<'a> {
|
||||
pub(crate) fn of(sk: &'a solidcraft_engine::sketch::Sketch) -> SnapPoints<'a> {
|
||||
let hide_linked = sk.view.hide_projected;
|
||||
let text = if hide_linked {
|
||||
Default::default()
|
||||
} else {
|
||||
// An id names its first link, as `Sketch::link` finds it.
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
sk.links
|
||||
.iter()
|
||||
.filter(|l| seen.insert(l.id.as_str()))
|
||||
.filter(|l| l.kind == solidcraft_engine::sketch::LinkKind::Text)
|
||||
.map(|l| l.id.as_str())
|
||||
.collect()
|
||||
};
|
||||
SnapPoints { hide_linked, text }
|
||||
pub(crate) fn of(sk: &'a solidcraft_engine::sketch::Sketch) -> Self {
|
||||
Self { hide_linked: sk.view.hide_projected, shown: solidcraft_engine::view::SketchPointVisibility::new(sk) }
|
||||
}
|
||||
|
||||
/// Does a face place snap to point `p` of the sketch?
|
||||
pub(crate) fn takes(&self, p: &solidcraft_engine::sketch::SPoint) -> bool {
|
||||
match p.link.as_deref() {
|
||||
None => true,
|
||||
Some(_) if self.hide_linked => false,
|
||||
Some(l) => !self.text.contains(l),
|
||||
}
|
||||
self.shown.takes(p) && !(self.hide_linked && p.link.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,9 @@ pub fn find(sk: &Sketch, moving: &[(usize, Vec2)], exclude: &[usize], mm_px: f64
|
||||
return keep(None);
|
||||
}
|
||||
let id = |i: usize| sk.points.get(i).map(|p| p.id.clone()).unwrap_or_default();
|
||||
let targets: Vec<(usize, Vec2)> = sk.points.iter().enumerate().filter(|(i, _)| !exclude.contains(i)).map(|(i, p)| (i, p.pos)).collect();
|
||||
let visible = solidcraft_engine::view::SketchPointVisibility::new(sk);
|
||||
let targets: Vec<(usize, Vec2)> =
|
||||
sk.points.iter().enumerate().filter(|(i, p)| !exclude.contains(i) && visible.takes(p)).map(|(i, p)| (i, p.pos)).collect();
|
||||
let free_curves: Vec<usize> =
|
||||
(0..sk.curves.len()).filter(|&c| sk.curves.get(c).is_some_and(|cu| !exclude.iter().any(|&p| cu.kind.uses(p)))).collect();
|
||||
let mut best: Option<(f64, Snap)> = None;
|
||||
|
||||
@@ -151,3 +151,96 @@ fn a_dragged_line_that_stops_short_stays_under_the_cursor_when_it_can_follow_aga
|
||||
assert!(start.dist(Vec2::new(x + 2.0, y)) < 0.1, "{start:?}");
|
||||
assert!(!h.app.session.model.is_stale());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn associated_spline_offset_hides_fit_nodes_and_follows_a_source_drag_live() {
|
||||
let mut h = Harness::new();
|
||||
h.app.home.open = false;
|
||||
run(&mut h, "sketch.create", json!({"plane":"XY"}));
|
||||
run(
|
||||
&mut h,
|
||||
"sketch.spline.fit_point",
|
||||
json!({
|
||||
"points":[[-17,15],[0,8],[17,13],[15,0],[5,-7],[-1,-14],[-9,-1],[-21,-1]],
|
||||
"periodic":true
|
||||
}),
|
||||
);
|
||||
let output = h.app.run("sketch.offset", json!({"curves":["s1"],"distance":2,"chain":true})).unwrap()["curves"][0].as_str().unwrap().to_owned();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 65.0;
|
||||
h.frames(3);
|
||||
let sid = h.app.session.active_sketch.unwrap();
|
||||
let original = h.app.session.world_state().sketch(sid).unwrap().sketch.clone();
|
||||
let source = original.curve_index("s1").unwrap();
|
||||
let derived = original.curve_index(&output).unwrap();
|
||||
let source_points = original.curves[source].kind.point_ids();
|
||||
let derived_points = original.curves[derived].kind.point_ids();
|
||||
assert!(derived_points.len() > 100, "exercise the crowded generated fit nodes");
|
||||
for &i in &source_points {
|
||||
assert!(solidcraft_engine::view::sketch_point_visible(&original, i));
|
||||
}
|
||||
let snaps = crate::dialogs::SnapPoints::of(&original);
|
||||
for &i in &derived_points {
|
||||
assert!(!solidcraft_engine::view::sketch_point_visible(&original, i));
|
||||
assert!(!snaps.takes(&original.points[i]));
|
||||
}
|
||||
let p = original.points[derived_points[32]].pos;
|
||||
let pos = screen(&mut h, p.x, p.y);
|
||||
let proj = crate::viewport::projection(&h.app, h.app.viewport.rect.unwrap());
|
||||
let hits = crate::viewport::pick_all(&h.app, &proj, pos);
|
||||
assert!(hits.iter().any(|hit| matches!(hit, crate::viewport::Hit::SketchCurve {id,..} if id == &output)));
|
||||
assert!(
|
||||
!hits
|
||||
.iter()
|
||||
.any(|hit| matches!(hit, crate::viewport::Hit::SketchPoint {id,..} if derived_points.iter().any(|i| original.points[*i].id == *id)))
|
||||
);
|
||||
let snap = crate::drag_snap::find(&original, &[(source_points[1], p)], &source_points, 0.01);
|
||||
assert!(
|
||||
!matches!(snap.map(|s| s.target), Some(crate::drag_snap::Target::Point(id)) if derived_points.iter().any(|i| original.points[*i].id == id))
|
||||
);
|
||||
crate::drag_snap::clear();
|
||||
|
||||
let before = h.app.session.doc.to_json();
|
||||
let undo = h.app.session.undo.len();
|
||||
let a = screen(&mut h, 0.0, 8.0);
|
||||
let b = screen(&mut h, 1.0, 9.0);
|
||||
let c = screen(&mut h, 2.0, 10.0);
|
||||
let mut path = vec![a];
|
||||
path.extend(steps(a, b, 4));
|
||||
press_and_move(&mut h, &path);
|
||||
for (pos, target) in [(b, Vec2::new(1.0, 9.0)), (c, Vec2::new(2.0, 10.0))] {
|
||||
h.app.synthetic.push(egui::Event::PointerMoved(pos));
|
||||
play(&mut h);
|
||||
assert!(h.app.viewport.point_drag.is_some());
|
||||
assert!(h.app.session.model.is_stale(), "the drag has not been released");
|
||||
let state = h.app.session.world_state();
|
||||
let sk = &state.sketch(sid).unwrap().sketch;
|
||||
assert!(sk.points[source_points[1]].pos.dist(target) < 0.1, "source follows the held pointer: {:?}", sk.points[source_points[1]].pos);
|
||||
assert_ne!(sk.exact_segs(derived), original.exact_segs(derived), "the offset follows before release");
|
||||
assert_eq!(sk.freeform_offsets[0].distance, 2.0);
|
||||
sk.validate_freeform_offsets().unwrap();
|
||||
let points = sk.curves[derived].kind.point_ids();
|
||||
assert!(points.iter().all(|i| !solidcraft_engine::view::sketch_point_visible(sk, *i)));
|
||||
for piece in sk.exact_segs(derived).unwrap() {
|
||||
for t in [0.0, 0.25, 0.5, 0.75, 1.0] {
|
||||
let p = piece.point_at(t);
|
||||
let distance = sk.exact_segs(source).unwrap().iter().map(|s| s.point_at(s.closest_param(p)).dist(p)).fold(f64::INFINITY, f64::min);
|
||||
assert!((distance - 2.0).abs() < 0.002, "live offset normal distance {distance}");
|
||||
}
|
||||
}
|
||||
}
|
||||
release(&mut h, c);
|
||||
assert!(!h.app.session.model.is_stale());
|
||||
assert_eq!(h.app.session.undo.len(), undo + 1);
|
||||
run(&mut h, "edit.undo", json!({}));
|
||||
assert_eq!(h.app.session.doc.to_json(), before);
|
||||
run(&mut h, "edit.redo", json!({}));
|
||||
run(&mut h, "sketch.offset.break", json!({"curve":output}));
|
||||
let state = h.app.session.world_state();
|
||||
let sk = &state.sketch(sid).unwrap().sketch;
|
||||
assert!(sk.curves[derived].kind.point_ids().iter().all(|i| solidcraft_engine::view::sketch_point_visible(sk, *i)));
|
||||
run(&mut h, "edit.undo", json!({}));
|
||||
let state = h.app.session.world_state();
|
||||
let sk = &state.sketch(sid).unwrap().sketch;
|
||||
assert!(sk.curves[derived].kind.point_ids().iter().all(|i| !solidcraft_engine::view::sketch_point_visible(sk, *i)));
|
||||
}
|
||||
@@ -853,8 +853,9 @@ fn pick_hits(app: &SolveApp, proj: &Proj, pos: Pos2, other_sketch_points: bool,
|
||||
for ss in point_sketches.iter().filter_map(|sid| st.sketch(*sid)) {
|
||||
let sid = ss.feature;
|
||||
let shown = snapping.then(|| crate::dialogs::SnapPoints::of(&ss.sketch));
|
||||
let visible = solidcraft_engine::view::SketchPointVisibility::new(&ss.sketch);
|
||||
for p in &ss.sketch.points {
|
||||
if shown.as_ref().is_some_and(|f| !f.takes(p)) {
|
||||
if !visible.takes(p) || shown.as_ref().is_some_and(|f| !f.takes(p)) {
|
||||
continue;
|
||||
}
|
||||
if let Some(sp) = proj.to_screen(ss.plane.to_world(p.pos)) {
|
||||
@@ -2586,10 +2587,11 @@ fn box_select(app: &mut SolveApp, proj: &Proj, bx: BoxSel, add: bool) {
|
||||
out.push(Sel::SketchCurve { id: c.id.clone() });
|
||||
}
|
||||
}
|
||||
let visible = solidcraft_engine::view::SketchPointVisibility::new(&ss.sketch);
|
||||
for (i, p) in ss.sketch.points.iter().enumerate().filter(|_| !off("sketch") && !off("sketch_points")) {
|
||||
if accepts.is_none_or(|a| a & crate::selection::SKETCH_POINTS != 0)
|
||||
&& i != 0
|
||||
&& solidcraft_engine::view::sketch_point_visible(&ss.sketch, i)
|
||||
&& visible.takes(p)
|
||||
&& bx.takes(&to2(&[ss.plane.to_world(p.pos)]))
|
||||
{
|
||||
out.push(Sel::SketchPoint { id: p.id.clone() });
|
||||
@@ -2763,8 +2765,9 @@ fn overlays(app: &SolveApp, painter: &egui::Painter, proj: &Proj) {
|
||||
let sk = &ss.sketch;
|
||||
let locked = sk.locked_points();
|
||||
let virtual_corners: Vec<usize> = sk.virtual_corners().into_iter().map(|(i, _)| i).collect();
|
||||
let visible = solidcraft_engine::view::SketchPointVisibility::new(sk);
|
||||
for (i, p) in sk.points.iter().enumerate() {
|
||||
if !solidcraft_engine::view::sketch_point_visible(sk, i) {
|
||||
if !visible.takes(p) {
|
||||
continue;
|
||||
}
|
||||
let hidden = (sk.view.hide_points && i != 0) || (sk.view.hide_projected && p.link.is_some());
|
||||
|
||||
Reference in new issue
Block a user