Commit Graph
121 Commits
Author SHA1 Message Date
dotCooCoo 71fcd584b0 Cutting Tools: limits count hidden clip art, and joined art is not refused
- Weed Box and Lines counts the outline points a nested clip group hides when
  it tests pieces for being inside a region, and counts the work of every clip
  group in the selection together, so a selection cannot run far past the
  limit.
- Stencil Bridges refuses art when its measuring budget runs out only while an
  island is still unjoined, and a refusal for size or detail says so.
2026-11-12 02:35:35 -08:00
dotCooCoo 2a494dec50 Cutting Tools: Stencil Bridges keeps the islands of a stencil attached
Object › Plug-ins › Cutting Tools › Stencil Bridges… (`addon.cutting.bridges`)
cuts strips of a set width out of the selected art so that a stencil cut
from it holds together: every island (the inside of an O, A or 8, a hole in
a shape) stays joined to the sheet, directly or through another island.
Bridges take the shortest crossings that fit first, then spread around each
island up to the count asked for, at least two widths apart. They run
vertically, horizontally or along the shortest crossing among 12
directions 15° apart.

- The art is measured by the area its outlines enclose, whatever their
  paint, with hidden objects left out; type by its glyph outlines, without
  converting the type. The result is one compound path with the strips cut
  out, painted as the topmost selected shape with outlines, in the place of
  the topmost selected object with outlines. Selected objects with no
  outlines in the art stay where they are.
- Keep Original (on by default) moves the selected objects that make up the
  art to a new hidden layer, Stencil Original; off replaces them.
- Art with no islands, or none a bridge of the width fits, is left as it is
  with a message and no undo step. Islands the cut result still encloses
  are counted in `unbridged`, with a message. Otherwise the run is one undo
  step.
- Curves are followed within a twentieth of the width (0.002 to 0.25 pt).
  Art that reaches more than 10000000 pt from the origin or has more than
  200000 anchors is refused before it is combined. Art is also refused when
  its combined area has more than 4000 outlines, when its outlines need
  more than 200000 straight segments, or when placing the outlines or
  joining every island takes more work than a fixed budget allows; when
  the budget runs out while extra bridges are added, the bridges found so
  far are kept.

Tests in vectorcraft-pathops, vectorcraft-engine and vectorcraft-ui-egui,
command_sweep cases, catalog rows in all 27 languages, docs/mcp.md, the
evidence list in docs/target-app-parity.md and the ROADMAP progress log.
2026-11-11 22:17:43 -08:00
BFlat ea3a4ec373 Merge pull request #3145 from adambelis/feat/width-smooth-profile
Width profiles run smooth between their points
2026-11-12 11:41:15 +07:00
BFlat feac9b5175 Merge pull request #6672 from dotCooCoo/fix/simplify-flat-loop-split-limit
Simplify: a flat closed loop keeps its outline when splitting reaches its limit
2026-11-12 09:13:25 +07:00
adambelis ff18e8c733 Width profiles: the edge fit stops once it is within tolerance (#3145) 2026-11-12 00:35:01 +01:00
dotCooCoo 1618686224 Path ops: rejoining split curves tests only the input curves around the run
exact_merge looked at every input curve on each merge attempt, so tidying
the output of a sweep cost the number of input curves for each joint it
tried to remove. It now takes its candidates from Tidy::curves_around,
the bounds index that repair already uses, and tests only the curves
whose bounds hold both ends of the run.

A new test cuts one curve of a 1,000-curve row in three and checks that
the merge rebuilds it while testing at most four curves; before the
change it tested 1,002.
2026-11-11 14:57:19 -08:00
dotCooCoo 883c24a3a8 Simplify: lines between samples of a flat loop stay within the tolerance
When the room left in the result limit is too small for the lines of a
curved segment to stay within the tolerance, the loop stays as it is
instead of taking fewer lines that can pass it.
2026-11-11 14:39:25 -08:00
dotCooCoo 0d600449d1 Simplify: the samples of a flat loop count its line segments in the limit
When a flat closed loop becomes the lines between samples of its curved
segments, the line segments it keeps now count toward the 2,097,152-line
limit before the rest is shared among the curved segments. When that
leaves no line for some curved segment, the loop stays as it is.
2026-11-11 14:31:33 -08:00
dotCooCoo 3edc1640fd Simplify: a flat closed loop keeps its outline when splitting reaches its limit
Follows #6651. When the refits of a closed subpath enclose no area,
Simplify splits its curves until straight lines between the pieces
enclose one. That split stopped at 65,536 pieces in all, and a loop
with many anchors then came back as the flat refit, with no outline.

- Only curved segments are split; line segments stay one piece each.
- When no split within the limits encloses an area, the loop stays as
  it is. With Convert to Straight Lines, each curved segment becomes
  the lines between its samples instead.
2026-11-11 13:17:39 -08:00
adambelis a9fd0eb7c0 Merge remote-tracking branch 'origin/main' into feat/width-smooth-profile
# Conflicts:
#	docs/target-app-parity.md
2026-11-11 22:13:44 +01:00
adambelis a08937661b Width profiles: near-outline sampling and review fixes (#3145) 2026-11-11 22:09:10 +01:00
dotCooCoo 796e2a7ebc Simplify: a closed subpath whose refits enclose no area keeps its outline
Follows #6614. When a closed subpath's refit in two and in three parts is
still lines that enclose no area, for example an S curve closed by a
straight edge whose cut points all lie on that edge, Simplify now splits
each segment in 2, 4, 8 and so on (up to 1,024 pieces, 65,536 in all)
until every piece is within the tolerance of the line between its ends
and those lines enclose an area. With Convert to Straight Lines those
lines are the result; without it the subpath stays as it is. A subpath
whose points are all in line keeps the refit's lines, so its extra
anchors are still removed. Lines count as enclosing no area when every
point is within 1e-6 pt of one line.
2026-11-11 11:00:51 -08:00
dotCooCoo 9ce1558c5e Simplify: a closed subpath whose refit is one curve stays in the path
A closed subpath that refits to a single curve has one anchor, which is
not a subpath, so Simplify dropped it: a small circle inside a compound
path was lost and the result ended hundreds of points from the original,
and a small closed path on its own came out empty. Such a subpath is now
refitted in two parts, cut at the point farthest from its start, or in
three when two parts would still enclose no area (two lines that retrace
each other with Convert to Straight Lines). A closed subpath of a single
segment stays as it is.
2026-11-11 08:21:15 -08:00
adambelis 1c43204d15 PR 3145: fixes gradient and stroke outline issues; resolves review items 2026-11-10 13:08:47 +01:00
dotCooCoo 4060f14ce0 Path ops: Unite sweeps a single path again when two segments lie within twice the sweep tolerance
The second sweep of `unite_all` joins contours that lie within the sweep
tolerance of each other, and the first sweep can leave such contours
apart. `solid::tests::meets_within_twice_the_boolean_tolerance` needs
that join. A single input now skips the second sweep only when no two
of its segments lie within twice the tolerance. The check compares at
most 512 pairs per segment and runs the second sweep past that.

`unite_parts` tests the single-input case before it builds the combined
path, and it takes `parts` and `inputs` by value so that both are freed
before the second sweep.
2026-11-09 16:19:01 -08:00
dotCooCoo 10909656d9 Path ops: Unite sweeps a single path once when its own sweep leaves its segments as they are
`unite_all` and `try_unite_all` swept each input under its own fill rule
and then swept all the resulting contours again under NonZero. With one
input whose sweep split and dropped none of its segments, the second
sweep ran over the same simple contours and doubled the time. Such an
input is now refit from the first sweep's contours. Inputs that cross
themselves and unions of several inputs are still swept again, and the
input paths are converted once instead of twice.
2026-11-09 13:20:20 -08:00
dotCooCoo 0945eaf2b3 Merge main into perf/contours-cover-lists 2026-11-08 15:00:56 -08:00
Drew Dibble bc87fd1b7e 3D effects: merged contours keep their parts, and edge cases are consistent
The boolean's answer went through orient(), which judges each ring's
side from its first point. The boolean often starts a contour where it
touches another, so a triangle whose tip rests on a square's edge was
dropped, and an even-odd hole touching its outline was filled. The
boolean already runs holes against their outlines, so its rings are now
only turned as a whole.

Other fixes:
- A bowtie drawn as one contour has no area of its own. It was dropped
  before the meet check and built nothing, although it fills both lobes.
  Sampling now keeps rings with no area, and orient() drops them after
  the check. Flat rings, every point on one line, fill nothing and are
  still dropped, so a stray flat contour doesn't send separate art
  through the boolean.
- An open subpath was closed along its end handles when the profile went
  through the boolean, but straight otherwise, as the renderer fills it.
  It is now closed straight before the boolean too.
- An empty union was taken as a refusal. Contours that cancel (a square
  drawn twice under even-odd) built a square. They now build nothing.
- Contours meet within twice the boolean's own sweep tolerance,
  computed by its rule from every anchor and handle of the path it is
  given. pathops snaps anchors onto nearby heights before the sweep
  joins edges within that tolerance, so contours up to about 1.4 times
  it apart can come out as one; within twice it, every contour it
  might join goes through it, even far from the origin, and the few it
  leaves apart cost a boolean that builds the same shape.
- A one-anchor subpath draws nothing, so it is skipped, as the renderer
  and the boolean skip it.
- A profile the boolean refuses reuses the rings already sampled.

Tests:
- touching parts and holes;
- an open subpath with handles, alone and beside crossing bars;
- a flat contour beside separate squares;
- contours joined far from the origin, including by handles alone,
  and contours a few millionths apart near it kept apart;
- triangles snapped together and joined at 1.4 times the tolerance,
  the same triangles left apart and kept whole, and the factor pinned
  between 1.9 and 2.2 times, with the worst case just past twice the
  tolerance left apart by pathops;
- a 24-bar grid: too complex for Extrude and Inflate, whole under Rotate;
- the merged rings turned as a whole;
- a one-anchor loop;
- the bowtie and the cancelling squares under all three effects;
- a 3,001-point star that the boolean refuses (TooComplex), built as
  drawn.

Docs: the pathops README's Limits section now says only the try_
functions refuse, lists OpenPath and the caught panic, and describes
unite_all's answers, and which calls take a precision. three-d.md and effects-parity.md (the 3D row, its
history and status line) describe the merge and its limits.
2026-11-09 02:24:47 +07:00
Drew Dibble 62a01689f0 3D effects: resolve a profile only when its contours meet
The boolean that merges crossing contours cost about as much as the rest
of a plain extrusion, and it ran on every profile. Most art has no
contours that meet, so the profile is now sampled first (its point limit
checked there, before any boolean) and resolved only when two edges
cross, touch, run along each other or fold back, in one contour or
across two. Edges are swept by their left ends, within a pair budget past
which the boolean decides. Touching counts: squares sharing an edge
inflate as one puff, not two with a seam.

On a release build, Extrude of 120 separate contours takes 0.31 ms
instead of 4.18 ms; profiles that do cross cost what they did.

Tests: the meeting cases, squares sharing an edge, the fallback for a
profile the boolean refuses, Inflate's extent and area for the crossing
k, and the coincident circles' area. The hairline test now holds a
crossing pair so that its profile still goes through the boolean.

Docs: the merge is described once for all three effects, linking to a new
Limits section in the pathops README instead of repeating its limits,
and the point limit notes that a merged outline can have more points.
2026-11-09 02:24:47 +07:00
dotCooCoo be0e9347b8 Path ops: Merge finds every fill's faces in a few contour passes instead of one per fill
Squashed from #6191 onto main.
2026-11-09 00:30:43 +07:00
dotCooCoo f74c7e8056 Path ops: region building lists the shapes covering each region instead of a flag per shape
Squashed from #6144 onto main.
2026-11-09 00:28:12 +07:00
dotCooCoo 9f629418e9 Merge main into perf/contours-cover-lists 2026-11-08 00:21:42 -08:00
dotCooCoo 96252edb74 Flattening: stop building a group's regions at the first one past the region limit
With an unclipped raster/vector balance between 0 and 100, a group that
splits into more than 10^(balance/20) regions is rasterized whole and
none of its regions are used. The region build now stops at the first
region past that limit (pathops `regions_within`) instead of building
every region and then discarding them. Groups at or under the limit,
and every group with Clip Complex Regions on, still build every region.

Flatten Transparency, the flattened document and EPS export come out
the same as with every region built, for groups under the limit, at it,
just over it and far over it.
2026-11-08 12:47:59 +07:00
dotCooCoo 8a7011a9d3 Path ops: stroke outlining and unions sweep long, low paths along their length
The overlap sweep moves a horizontal line down the page, and its time
per event grows with the number of segments on that line. A long, low
path, such as rows of a zigzag, puts a whole row on the line at once.

stroke_region, unite_all_with and try_unite_all_with now estimate how
many segments the sweep holds at once in each direction, from up to
4,096 segment boxes (one from each run of segments, at a position that
changes from run to run). When the estimate for the input turned a
quarter turn is under two thirds of the upright one (2,048 segments or
more), the operation runs on the turned input and the result is turned
back. A quarter turn swaps and negates coordinates, so no point moves
by rounding. Offset Path is not turned. Callers of unite_all,
try_unite_all and outline_stroke take the turned sweep through these
functions.

Tidy's lookups of the input curves around a piece and of the kept
anchors can search along y: once lookups with more than 64 candidates
along x have scanned 16 per item, a lookup sorts the list along y once
and uses that window when it holds fewer candidates (under a quarter as
many for curves). Where the y window rarely helps, only one lookup in
32 measures it. is_kept counts the few points within its tolerance
directly instead of searching for the window's end.

In the sweep_axis tests, a turned result has as many subpaths as the
result without the turn, and the area where the two differ is no
larger than the refit precision times the outline's length.

On one open path of 200,000 anchors in rows of 1,000, Outline Stroke
takes 17.3 s of processor time instead of 31.3 s and Unite 2.9 s
instead of 6.5 s in a release build. Unite of 2,000 circles takes the
same time as before.
2026-11-08 12:22:04 +07:00
dotCooCoo 500f74b700 Tests: the index read count includes entries read from more than one cell
RectIndex::at notes its reads before it sorts and removes repeats, so an
entry filed under several of the cells a lookup spans counts once per cell.
2026-11-08 11:42:47 +07:00
dotCooCoo 4cf4ea3a39 Tests: the shape builder and large edit guards count work instead of timing it
The Shape Builder, Live Paint and run_around tests in planar/tests.rs
measured elapsed time against a 5 s limit, and the large edit tests
(large_edits.rs, with the Lock and Hide All Artwork Above and perspective
Alt-drag tests that share `soon`) against 20 s. On a loaded machine they
failed without any change in the code.

- pathops: a test-only counter adds up the items `RectIndex::at` reads
  and the segments `nearest_u` searches. The four planar tests assert a
  bound on that count.
- doc: `tree_walks` counts, per thread, the walks of a document's tree
  that lookups by id make. `soon` fails when a command walks the tree
  more than 64 times, and keeps a 300 s wait for a hang. The tests that
  named a 20 s limit are renamed.

Looking objects up one at a time in Delete or Offset Path, or scanning
the whole index or ring in the planar code, makes these tests fail.
2026-11-08 11:42:46 +07:00
dotCooCoo bc19dd64db Simplify: Convert to Straight Lines keeps the tip of a path that turns back past its end
`rdp` measured each sample to the infinite line through the ends of the
line that would replace it. A sample beyond either end could be far from
that line segment and still be dropped: a line to (100, 0), a 0.2 pt
U-turn and a line back to (50, 0.2) became one line at a tolerance of
1 pt, leaving the tip 50 pt away. `rdp` now measures to the segment, so a
sample beyond an end is measured to that end, and a zero-length chord
measures to its point as before.

The test `a_path_that_turns_back_past_its_end_converted_to_straight_lines_keeps_its_tip`
covers that path.
2026-11-08 11:22:30 +07:00
dotCooCoo 4a7d732197 Simplify: smoothing a densely sampled path takes time in proportion to its samples
Object > Path > Smooth weighed every sample within three spreads of each
point, so the work grew with the square of the anchor count on traced
paths: 360 steps per sample for 1,000 anchors and 2,949 for 16,000.
Consecutive samples within 1/16 of a spread of each other are now
weighed together as their count at their mean place, found through a
tree of sums over the run's samples. A traced outline takes 203 to 286
steps per sample from 1,000 to 64,000 anchors, and every smoothed point
stays within a thousandth of a spread of weighing the samples one by
one (300 random runs).
2026-11-08 11:22:29 +07:00
dotCooCoo 199c4c3467 Simplify: fitted curves stay within the tolerance of the path, and serpentines simplify faster
The curve fitter split a piece over the tolerance at its worst point and
accepted any piece at depth 41 whatever its error. A worst point near the
end of a piece peels off a few points, so a long path refitted nearly all
of its points at every level. A serpentine of 200,000 anchors kept pieces
with a fitted error of up to about 1,598 pt at a tolerance of 1 pt.

Simplify also took at most 256 samples per segment, so the lines between
the samples on a long arc could be farther from it than the tolerance.

- `fit_rec` moves a split that is near an end of its piece to two fifths
  of the piece from that end, so the depth is logarithmic, and it keeps no
  piece over the tolerance. A piece of 32 points or more is first fitted
  to about 16 of them, and it is split when that fit is over the tolerance.
- A curve is also rejected when it is over the tolerance from the lines
  between the points it fits, with samples close enough that it stays
  within 1% of the tolerance of the chord between them. A pair of points
  that would need more than 1,024 samples counts as too far.
- `join_pieces` joins neighboring pieces when one curve fits them, up to
  33 pieces per curve.
- `sample_steps` takes enough samples of a curved segment to keep it within
  0.5% of the tolerance of the lines between them. A segment that would
  need more than 65,536 samples stays as it is, and so does a segment
  after a run's first 16,777,216 samples or 2,097,152 result segments. With
  Convert to Straight Lines such a segment becomes evenly spaced lines, up
  to 65,536. A fit holds at most 1,048,576 samples, so a longer run is
  fitted in parts.
- Simplify fits at 98% of the tolerance, since turning a nearly straight
  curve into a line moves it by up to 1.5%.
- Above slider level 0, Object > Path > Smooth fits a whole closed path
  with no corner along one direction across its start, when no segment
  stays as it is and the path is fitted as one part. A direct-selected
  section does not get this. The start can still have a kink: a first or
  last fitted curve that is nearly straight becomes a line, which leaves
  along its chord, and a refit with more anchors than the path is
  discarded, so the path stays as it is.

A closed subpath that refits to one curve is still dropped, as on main, so
it can end up farther than the tolerance from the result. docs/mcp.md
states this.

The anchors of every simplified path can differ from before, and so can
the results of the Pencil, Blob Brush and Smooth, which use the same fit.
2026-11-08 11:22:29 +07:00
dotCooCoo 72c46b8aa2 Path ops: boolean contour passes test the shapes covering each face instead of a flag per shape
The contour passes of the two-path boolean operations, Offset Path and
the many-shape boolean (`boolean_n`, used by compound shapes and
Intertwine) built a flag for every input shape for each winding they
tested. They now test the list of inputs that cover the face, ascending,
refilled in one buffer per pass, so a test costs the size of the cover
rather than the number of shapes. `boolean_n` and `try_boolean_n` take
that list; the compound shape and Intertwine tests read it directly.

The contours, their order and the output are the same: a new test
compares every pass's test with the flag form on overlapping, nested,
touching and apart squares and on 72 overlapping squares, and another
checks that a pass over 100 squares apart hands its test one input at
most.
2026-11-07 10:28:25 -08:00
dotCooCoo 3fcd157f17 Path ops: region building lists the shapes covering each region instead of a flag per shape
Finding the distinct regions of an arrangement built a flag for every
input shape at each edge and kept them in a sorted set, so it cost edges
times shapes in time and memory. Each coverage is now the sorted list of
the shapes that cover it, deduplicated in a hash set and sorted into the
order the flag masks had, so regions, their order and the output of
Divide, Hard Mix, Soft Mix, Shape Builder and the flattener are the same.
The face lookup and Pathfinder merging take the same lists.

Tests compare the lists and the regions with the flag masks for
overlapping, nested, touching and disjoint shapes and for 72 shapes.
2026-11-07 08:44:50 -08:00
dotCooCoo 2e8e8a1909 Offset Path: the spurious-contour check tests only the source segments near each contour
The check tested one box per source segment for every candidate hole or
island. It now searches a tree of the segments' bounding boxes, built once
when the first contour is checked. The answer for each contour is the same,
so the output paths do not change. MAX_OFFSET_SEGMENT_PAIRS does not change.
2026-11-07 08:38:12 +07:00
Don Omondi 6725f24a80 Path booleans: check the windings against the operands' areas and resolve the operands when they don't add up (#5478) 2026-11-06 23:03:25 +07:00
el-j 08ff029ac2 fix(pathops): bound offset path complexity and handle arrangement failure safely (#4145)
Squashed from #4345 onto main.
2026-11-05 19:40:58 +07:00
el-j e6ac6cd3ba perf(pathops): build Trim and Crop on each_classified_face (#4503) (#4511)
Thanks @el-j — merging.

**Summary:** Pathfinder Trim/Crop (and Merge) now build on `each_classified_face` (generalizing `each_face`) so classification and face construction share one batched pass, dropping the duplicate `batched_classified_contours` after #4375 as @bflatastic asked. Differential tests lock subpath order and the fully-covered shape case.
2026-11-05 12:22:02 +01:00
dotCooCoo 19d9843971 Shape Builder: a merge or erase takes the stretch of every coincident line
Where two open paths lie along the same stretch, the planar map gave
that stretch to one of them only, so a merge or an erase across it left
the other path in place there. Every other open path along an edge now
has its own piece in `lines_under`, under a closed path's outline or
along a line in front. `lines`, `edges` and Live Paint's edges stay one
per stretch, and an Alt-click there still takes the piece in front.
2026-11-05 14:12:18 +07:00
bflatastic b814fcf689 Pathfinder: holes that touch at a point stay holes of their region
The Live Paint faces were right; the Pathfinder regions over-counted.
Two small circles inside a big one, touching each other at one point,
gave the big circle's region only one of its two holes, and the other
hole came out as a region of its own.

linesweeper walks the boundary of the two touching holes as one loop
and, when the walk comes back to the shared point, cuts the second
hole off as a child of the first and flags it outer, as if it were an
island inside it. face_groups trusted that flag, so the hole started a
face. The same cut goes wrong the other way when an outer contour
touches its own hole: the outer contour comes out as an island inside
the hole.

Such a loop winds the same way as the contour it was cut from, against
its flag. face_groups now checks every contour's flag against its
winding; a pass where any disagrees is grouped by geometry instead
(each hole goes with the smallest outer contour of its mask around it).
Passes where they all agree keep the sweep's tree as before, at the
cost of one area per contour.

Adds deterministic tests for both shapes of the bug and checks in the
proptest regression seed.
2026-11-05 03:02:03 +07:00
dotCooCoo f7bebdeaf0 Live Paint: preserve face colors at shared curved boundaries
Capture sweep-piece source attribution before endpoint pinning can move its
midpoint off the original outlines. Keep the existing lookup after pinning
when the original piece has no source.

Add planar coverage and native compound Make/Expand/undo regressions, and
document the existing front-most source-fill rule.
2026-11-04 14:08:23 +07:00
Dylan M. Taylor 13b0bbe639 Linear-time scans: Add Anchor Points, swatch library adds, placed-document export, width profiles, symbolism previews
Add Anchor Points builds the new anchor list in one pass, Swatch Library add looks names up in sets, full_documents dedups placed keys with a set, width profile lookups and mark insertion use binary search, and the symbolism tools cap and thin the previews of a long drag as Liquify does.
2026-11-04 08:13:18 +07:00
vangel76andClaude Sonnet 5.5 63aeefbd6c Offset Path: scale the box slack with the coordinates and tighten the tests (#5455)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:13:15 +07:00
vangel76andClaude Sonnet 5.5 445e954ec9 Offset Path: skip far segments and stop at the first near one in the spurious-contour check (#5455)
For every hole (when growing) or island (when insetting), the check measured the
distance to every segment of the input with the nearest-point search and took the
minimum, so the cost grew with holes x segments. Segment bounding boxes are now
computed once, a segment whose box is farther than the offset distance is skipped,
and the search stops at the first segment that is close enough. The decision is the
same as before; a test compares it with the old full scan on random segment sets.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:13:15 +07:00
vangel76andClaude Sonnet 5.5 0a2935cf8f Booleans: limit of one unit, crop outside shortcut, tests (#5465)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:13:12 +07:00
vangel76andClaude Sonnet 5.5 e951095617 Path booleans: refuse out-of-range input, guard the contour pass, cuts report refusals (#5465)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 08:13:12 +07:00
Dylan M. Taylor 1574dfe729 fix: anchor positions and Simplify measure curves through the guarded arc length 2026-11-03 22:17:45 +07:00
dotCooCoo 79c5218f8a Fix #5161: Shape Builder merges across a line under a merged shape's edge
Where a line lies under the outline of a closed path in front of it (such as
a shape an earlier merge made, which takes the place of the art in front of
the lines), the planar map gave that stretch only to the closed path, so the
line had no piece there. A merge or an erase across it left the line whole,
and the next map split the new shape along it into two regions.

- pathops: each planar edge records every input it lies on, and
  BuilderArrangement::lines_under holds the lines' pieces under a closed
  outline in front of them. lines, edges and Live Paint are unchanged.
- tools: BuilderMap adds those pieces to its lines, so a merge or an erase
  removes the stretch between the regions it takes, as it does elsewhere.
  An Alt-click where a line lies under an edge hits the one in front.
2026-11-02 14:32:57 +07:00
bflatastic 9e3f8dccec Fix #4950: Path Precision Tools: Divide into Dashes
addon.pathtools.divideDashes cuts the selected paths (or only their
direct-selected segments) into separate open paths of a dash length
measured along the curve, with an optional gap (gap pieces dropped
unless removeGaps is false) and Fit to Corners and Ends; pieces keep
the path's appearance, one undo step, at most 100000 pieces. Geometry
in vectorcraft-pathops; dialog with unit fields and Preview; tests,
sweep cases, catalog rows and docs.
2026-11-01 02:44:16 +07:00
bflatastic f0da383749 Smooth: the slider smooths bumps and jags into curves, and smooths only a direct-selected section 2026-10-31 23:10:56 +07:00
Don Omondi acb5a9721b pathops: simplify the add-anchors segment check for clippy's nonminimal_bool
Pre-existing lint on main that stops cargo xtask ci at clippy; the same
one-line change other open PRs carry.
2026-10-31 19:43:17 +07:00
bflatastic 79f7b393e9 Add-ons: Path Precision Tools — Shared Edge Merge
Path Precision Tools gains the Shared Edge Merge Tool, kept out of the
toolbars (All Tools drawer and Object › Plug-ins › Path Precision Tools,
no shortcut), and Keep Shared Points Together:

- Shared Edge Merge Tool (sharedEdge): over two paths touching at two
  coincident anchors (tolerance 0 to 3 pt) hovering shows the two
  stretches and the merged edge; a click replaces both with it, half way
  between them, or following the upper (Shift) or lower (Alt) path. Both
  paths stay closed or open, anchors either stretch has inside are
  matched on the other, one undo step.
- Making ends: near anchors meet on a click and an anchor one path lacks
  is added to match; Shift+Alt asks for this inside a stretch.
- Keep Shared Points Together: an option that makes Slide Anchor Along
  Path, Extend Path and Set Anchor take coincident anchors along. Nothing
  else in the app changes.
- Shared Edges section in the Anchor Inspector: brush diameter,
  coincidence and the option.
- Commands addon.pathtools.mergeEdge and movePointsTogether, params
  validated and covered by the command sweep.
- Cursor stand-in, catalog rows in every language, docs and tests.
2026-10-31 07:16:22 +07:00
bflatastic e13c4bc9d0 Add-ons: Path Precision Tools — live path effects 2026-10-31 06:10:47 +07:00