Merge pull request #6976 from dotCooCoo/fix/followup-6847

Cutting Tools: weeding lines stop at an outline that lies on a line
This commit is contained in:
BFlat
2026-11-13 08:29:58 +07:00
committed by GitHub
4 changed files with 27 additions and 7 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Weed Box and Lines…",
["Object", "Plug-ins", "Cutting Tools"],
None,
"{margin?: pt 0..100000 (9: from the art to the weed box), box?: true (false: the weeding lines only), direction?: \"horizontal\" | \"vertical\" | \"both\" (\"both\"), count?: whole number 0..1000 of lines each way (1), spacing?: pt (instead of count: as few lines as keep them at most this far apart), gap?: pt 0..100000 (0: the lines stop at the art's outlines), weight?: pt 0.01..1000 (0.3), swatch?: a color swatch the lines are stroked in (\"[Registration]\")} add a weed box around the selected art and weeding lines across it that divide it evenly, each running from one side of the box to the other and left out over the art and within `gap` of its outlines, so the waste lifts off in pieces. The art is measured by the outlines a cutter cuts: closed paths enclose their area by their fill rule and open paths are lines, compound paths by all their subpaths, type by its glyph outlines, a clip group by what shows inside its clipping path, images by their frames, placed files by the art they show (by their boxes when the file can't be read), symbol instances by their art and live objects by the art they draw; strokes, effects and hidden objects are left out. One group named Weeding, stroked with no fill, on top of the current layer (outside the clip groups around a group edited in isolation), selected, as one undo step → {id: the group, box: its weed box or null, lines (pieces of weeding line)}",
"{margin?: pt 0..100000 (9: from the art to the weed box), box?: true (false: the weeding lines only), direction?: \"horizontal\" | \"vertical\" | \"both\" (\"both\"), count?: whole number 0..1000 of lines each way (1), spacing?: pt more than 0 and at most 100000 (instead of count, never with it: as few lines as keep them at most this far apart, up to 1000 each way), gap?: pt 0..100000 (0: the lines stop at the art's outlines), weight?: pt 0.01..1000 (0.3), swatch?: a color swatch the lines are stroked in (\"[Registration]\")} add a weed box around the selected art and weeding lines across it that divide it evenly, each running from one side of the box to the other and left out over the art and within `gap` of its outlines, so the waste lifts off in pieces. The art is measured by the outlines a cutter cuts: closed paths enclose their area by their fill rule and open paths are lines, compound paths by all their subpaths, type by its glyph outlines, a clip group by what shows inside its clipping path, images by their frames, placed files by the art they show (by their boxes when the file can't be read), symbol instances by their art and live objects by the art they draw; strokes, effects and hidden objects are left out. One group named Weeding, stroked with no fill, on top of the current layer (outside the clip groups around a group edited in isolation), selected, as one undo step → {id: the group, box: its weed box or null, lines (pieces of weeding line)}",
has_selection,
weed
)]
@@ -723,12 +723,13 @@ pub(super) fn lines(region: &Region, outline: &[Segment], frame: Rect, n: Counts
continue;
}
// The lines (0-based) each outline segment comes within `gap` of: lines k (1-based) lie at
// v0 + k·step.
// v0 + k·step. The range is widened by a small tolerance so that a line whose position
// rounds just past a segment's end is still measured; `near` drops lines the segment misses.
let reach = |&(a, b): &Segment| {
let (va, vb) = (uv(a, vertical).1, uv(b, vertical).1);
let (lo, hi) = (va.min(vb) - gap, va.max(vb) + gap);
let first = ((lo - v0) / step).ceil().max(1.0);
let last = ((hi - v0) / step).floor().min(count as f64);
let first = ((lo - v0) / step - 1e-9).ceil().max(1.0);
let last = ((hi - v0) / step + 1e-9).floor().min(count as f64);
if step > 0.0 && first <= last { (first as usize - 1)..(last as usize) } else { 0..0 }
};
// The outlines near each line, found once for all of them, by index into `outline`.
@@ -541,6 +541,24 @@ fn indexed_crossings_find_the_same_stretches() {
}
}
/// An outline that lies on a weeding line stops it even when the line's position rounds just past
/// the outline's: the one line across 0.1 to 0.5 lies at 0.1 + 0.2, which is 0.30000000000000004,
/// and (0.30000000000000004 - 0.1) / 0.2 is 1.0000000000000002.
#[test]
fn an_outline_on_a_rounded_line_position_stops_the_line() {
let doc = vectorcraft_doc::Document::new(800.0, 600.0);
let c = 0.1 + 0.2;
let mut p = BezPath::new();
p.move_to((20.0, c));
p.line_to((80.0, c));
let n = Node::path(NodeId(1), PathData::from_bezpath(&p), Appearance::default());
let region = super::geometry::Reader::new(&doc).read(&[&n]).unwrap().unwrap();
let outline = region.outline().unwrap();
let counts = super::geometry::Counts { horizontal: 1, vertical: 0 };
let pieces = super::geometry::lines(&region, &outline, Rect::new(0.0, 0.1, 100.0, 0.5), counts, 0.0, 100).unwrap();
assert_eq!(pieces, [(Point::new(0.0, c), Point::new(20.0, c)), (Point::new(80.0, c), Point::new(100.0, c))]);
}
#[test]
fn art_nested_too_deep_is_refused() {
// A rectangle inside `levels` groups, each holding the next. A document keeps none this deep,
+4 -3
View File
@@ -10180,9 +10180,10 @@ selected art and weeding lines across the box, so the waste vinyl around the art
VectorCraft's own. The weed box is a rectangle `margin` points (0 to 100000, default 9) outside the art's bounds;
`box: false` leaves it out. The weeding lines run `"horizontal"`, `"vertical"` or `"both"` ways (default `"both"`) from
one side of the box to the other and divide it evenly: `count` lines each way (a whole number from 0 to 1000, default 1),
or with `spacing` instead, as few lines as keep them at most that far apart. A line is left out over the art's area and
within `gap` points of its outlines (0 to 100000, default 0: the lines stop at the outlines), so it ends in several
pieces, each a straight path. The lines and the box are stroked `weight` points wide (0.01 to 1000, default 0.3) in the
or with `spacing` points instead (more than 0 and at most 100000), as few lines as keep them at most that far apart. A
`spacing` that needs more than 1000 lines each way is refused, and so is a request that gives both `count` and
`spacing`. A line is left out over the art's area and within `gap` points of its outlines (0 to 100000, default 0: the
lines stop at the outlines), so it ends in several pieces, each a straight path. The lines and the box are stroked `weight` points wide (0.01 to 1000, default 0.3) in the
color swatch `swatch` (default `"[Registration]"`), with no fill.
The art is measured by the outlines a cutter cuts. Closed paths enclose their area by their fill rule, so the hole of a