mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 21:27:00 -06:00
Path: Remove Anchor Points is Object › Path's one item and shares the Delete Anchor Point refit (review of #241)
- Parity: Object › Path › Remove Anchor Points now runs the new command (`path.removeAnchors`,
the menu's feature name) instead of `path.deleteAnchors`, which deleted the anchors with their
segments and opened closed paths; that stays the Delete key's command ("Delete Anchor Points",
off the menu). No second "Smart" item.
- Geometry: the refit moves to `vectorcraft_pathops::remove_anchor` on the existing least-squares
fitter (fixed end tangents, a golden-section search for where the curve passes the removed
point) instead of a ~1,000-candidate grid search in geom (about 4 ms per anchor, now well under
0.1 ms). Two straight sides stay one straight side (a rectangle loses a corner and becomes a
triangle), as Illustrator does; non-finite input leaves the handles alone.
- The Delete Anchor Point tool (`path.removeAnchor`) uses the same function, replacing its
heuristic handle stretch; the explicit id/anchor and `fit` params of the new command are dropped
as duplicates of it. The command is enabled by direct-selected anchors (`has_anchors`), not a
UI override.
- The Control bar shows an icon button (the Delete Anchor Point icon, tooltip "Remove Anchor
Points") instead of a 204 pt text button; the labels reuse the translated "Remove Anchor
Points"; zh-hant gains "Delete Anchor Points". The Pen test that pinned Alt-click as plain Pen
(Illustrator's Alt over an anchor is Convert Anchor Point) is dropped.
- Tests for the refit, closed paths, straight sides and bad input; docs/mcp.md and ROADMAP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f5c3ecb47c
commit
d5a060a009
@@ -437,3 +437,49 @@ fn timing_unite_1000_circles() {
|
||||
println!("unite 1000 circles: {} ms, {} anchors", t.elapsed().as_millis(), r[0].path.anchor_count());
|
||||
assert!(!r.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_anchor_refits_the_curve_round_it() {
|
||||
use vectorcraft_geom::Anchor;
|
||||
let (p0, p3) = (Point::new(0.0, 0.0), Point::new(100.0, 0.0));
|
||||
let original =
|
||||
SubPath::new(vec![Anchor::with_handles(p0, p0, Point::new(30.0, 80.0)), Anchor::with_handles(p3, Point::new(70.0, 80.0), p3)], false);
|
||||
let mut sp = original.clone();
|
||||
let i = sp.insert_anchor(0, 0.4);
|
||||
assert!(remove_anchor(&mut sp, i));
|
||||
assert_eq!(sp.anchors.len(), 2);
|
||||
assert!(sp.anchors[0].h_out.distance(original.anchors[0].h_out) < 0.5, "{:?}", sp.anchors[0].h_out);
|
||||
assert!(sp.anchors[1].h_in.distance(original.anchors[1].h_in) < 0.5, "{:?}", sp.anchors[1].h_in);
|
||||
// A circle stays closed and round with one anchor fewer.
|
||||
let mut c = circle(0.0, 0.0, 50.0).subpaths.remove(0);
|
||||
let n = c.anchors.len();
|
||||
assert!(remove_anchor(&mut c, 1));
|
||||
assert!(c.closed && c.anchors.len() == n - 1);
|
||||
let a = ar(&PathData::single(c));
|
||||
assert!(close(a, PI * 2500.0, 0.03), "{a}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_anchor_between_straight_sides_draws_a_straight_one() {
|
||||
let mut sp = rect(0.0, 0.0, 100.0, 80.0).subpaths.remove(0);
|
||||
let n = sp.anchors.len();
|
||||
assert!(remove_anchor(&mut sp, 0));
|
||||
assert!(sp.closed && sp.anchors.len() == n - 1);
|
||||
assert!(!sp.anchors.iter().any(|a| a.has_in() || a.has_out()), "{sp:?}");
|
||||
let mut line = SubPath::polyline(&[Point::new(0.0, 0.0), Point::new(40.0, 30.0), Point::new(100.0, 0.0)], false);
|
||||
assert!(remove_anchor(&mut line, 1));
|
||||
assert!(line.anchors.len() == 2 && line.segment_is_line(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_anchor_at_an_end_drops_its_segment_and_rejects_bad_input() {
|
||||
let mut sp = SubPath::polyline(&[Point::new(0.0, 0.0), Point::new(10.0, 0.0), Point::new(20.0, 5.0)], false);
|
||||
assert!(!remove_anchor(&mut sp, 3));
|
||||
assert!(remove_anchor(&mut sp, 0));
|
||||
assert_eq!(sp.anchors[0].p, Point::new(10.0, 0.0));
|
||||
// Non-finite points never panic.
|
||||
let mut bad = SubPath::polyline(&[Point::new(0.0, 0.0), Point::new(f64::NAN, 1.0), Point::new(f64::INFINITY, 0.0)], false);
|
||||
bad.anchors[0].h_out = Point::new(5.0, f64::NAN);
|
||||
assert!(remove_anchor(&mut bad, 1));
|
||||
assert_eq!(bad.anchors.len(), 2);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user