Commit Graph
126 Commits
Author SHA1 Message Date
ajandClaude Fable 5.2 9b7082e92f fix(clone): keep Shift+click copies at the part spacing
The coarse bounding-box pass only stopped at obstacles whose box lay in
the push path, so a copy could drop past a neighbor beside the path and
end inside its spacing. A box push now also stops at an obstacle nearer
than the spacing to the side of the path.

A push ignores a part the copy starts on, so a settle could slide out of
that part and stop clear of it yet inside its spacing. SettlePlacement
now prefers a push order that keeps the part spacing, and a named order
that ends too close falls back to the other order.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 15:43:47 -05:00
ajandClaude Opus 5.5 cafb34b510 feat(clone): choose Shift+click push order and add box-only Ctrl+click
Shift+click in Clone Parts always kept whichever axis order landed the
copy nearest the origin, which can park it in a pocket the user didn't
want. Options > Clone push placement now picks Closest to origin (the old
behavior, still the default), Push left then down, or Push down then left.

Ctrl+click settles the copy on bounding boxes alone, so it stops square
to its neighbors' boxes instead of nesting into their shapes. Shift+click
keeps its box pass then geometry settle; both follow the chosen order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 07:53:24 -05:00
ajandClaude Opus 5.5 beccbae106 perf(fill): skip copy-distance searches whose result is thrown away
One Irregular decode of a small job took seconds because each block in the
catalog is built by the full Fill pipeline (PrivatePlateFill), and inside it
StripeFiller's estimate rows spent about 85% of the CPU in FillLinear's
perpendicular TilePattern. A stripe estimate is exactly one pattern tall, so
the pattern's copy distance, an all-pairs slide search, always exceeded the
room left and the copies were discarded after the search had run in full.

TilePattern now passes FindPatternCopyDistance the largest distance that
could still place a copy: for a whole copy, the room past the pattern; for a
pattern that may tile partially, the most room past any one of its parts. The distance
is the maximum over every pair, so the search stops as soon as it passes that
bound and the tile returns empty, which is what the full search led to. The
public CopyDistance is unchanged. StripeFiller's angle searches also reuse
each rotated pattern's bounding box per angle instead of cloning and
rotating the parts again on every probe.

Output is identical: the layouts of every benchmark job that finishes match
the previous build. Measured A/B on this machine, alternating builds per job
(Benchmark --engines Irregular --parallel 1, benchmark ms):

- N26-2A5: 15.5 s -> 8.8 s (12.6 s -> ~7.2 s in quieter runs)
- 73 of the 80 bench jobs (the other 7 time out at 5 minutes in both
  builds): 1,990 s -> 1,578 s total (-21%), process CPU 6,486 s -> 3,842 s
  (-41%), median job 0.91x. N26-295 now finishes in 242 s with the same
  layout where it hit the 5-minute limit before.
- Problem-nest corpus auto-nest and fill areas: all at baseline.

A parallel herringbone pair build was tried and dropped: it saved 1.2 s on
N26-2A5 but cost 10 s and 45 s more CPU on P241017-01.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 18:08:12 -05:00
ajandClaude Opus 5.5 6867ae2ee4 feat(arrange): let Fix Spacing clear overlaps on packed plates and tilt as a last resort
On a plate packed edge to edge, an overlap left by hand-placing extra
parts could not be fixed: straight push chains ran into the edge and
the relaxation gave up on pairs that share material, which have no
contact normal. The relaxation now pushes such a pair along its
shortest straight way out and lets the rest settle, which cleared a
0.92 sq in overlap on a 38-part plate with every part kept square.

When moves alone still cannot settle, the relaxation runs again as a
rigid one: an off-center push also turns a part, up to 3 degrees, if
its drawing is free to rotate. Moves stay first because a tilt turns
every part the push reaches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:45:54 -05:00
ajandClaude Opus 5.5 821c97e8dc feat(arrange): add Fix Spacing to push apart only flagged parts
Expand Spacing opens up a whole selection; nests that are a hair short
of fitting need the opposite: move only the parts the overlap check
flags, by the shortfall, and leave everything else where it is.

Fix Spacing pushes one part of each flagged pair straight away from the
other and ripples the push into neighbors it crowds, so rows already
locked in at the spacing can make room. When a plate is packed edge to
edge and no straight push fits, a constraint relaxation shares each
shortfall between both parts along the closest-point normal. Every
result is confirmed by the overlap check before any part moves, and is
applied only when it has fewer findings and nothing new.

NativeContours gains a coarse-flattening overload, Translated, Nested
and a closest-points GapTo for the search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:29:10 -05:00
ajandClaude Opus 5.5 7dc471bbf8 fix(engine): keep Strip layouts clear of clamp keep-outs
The Strip whole-job engine laid its strips, settled them and picked its pack
remnant from the bare work area, never adding the plate's clamp keep-outs the
Lattice orchestration uses. On a clamped sheet it put parts over the clamp, the
runner's layout check rejected the candidate, and Strip produced nothing (N26-2A5
rev 5: door stop at (21.09,1.50) over clamp-1).

NestStrips now treats InteractiveClamps.ClearanceRectangles as occupied space for
the iterative shrink strips (through RemnantFiller), settles with the clamp
obstacles when there are zones, and finds the pack remnant around them even
before any strip is placed. Unclamped plates take the old path unchanged.

A FixedStrategyNestingEngine theory runs all four fixed strategies on a clamped
26 x 26 sheet; only Strip failed it before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:59:21 -05:00
aj 49603eddf9 fix(geometry): nest the same on Windows and Linux
Geometry, nesting and import code now call PortableMath instead of
System.Math for Sin, Cos, Tan, Atan, Atan2, Asin and Acos (Core, Engine,
IO, Data; 269 calls in 64 files). A test fails if those projects call the
runtime's trig again.

With the runtime's trig, Windows and Linux disagreed in the last bit at
non-quarter angles and the same job could nest differently: the
learned-angle nests (a cross fixture) placed 23 parts in Strip on Windows
and 28 on Linux. Now every Engine.Tests pin gives the same layout on both
(3600 passing on Linux and on DesktopPC), Linux layouts are unchanged, and
the learned-angle rows are re-pinned to the shared layouts, which pass the
overlap and work-area checks, and run on every platform again.

Fixes Project Memory 518f3b58.
2026-11-09 19:18:00 -05:00
ajandClaude Opus 5.5 735c076afb feat(fill): tile herringbone pairs at the part's hull angles
Fill paired a 9" angle bracket at 90/270 degrees (75 on a 48 x 96 plate)
while a hand-laid herringbone at 315/135 holds 84. Its pair touches only at
the legs' ends, so it ranked ~888th by pair box area, and pairs are tiled
at the pair's own hull angles, never the part's 45-degree edge.

The pairs phase now also builds herringbone pairs: the part turned so a
hull edge lies along a sheet axis, its 180-degree copy slid onto the row
the part makes along that axis, tiled and remnant-filled like best-fit
pairs. Fill now finds the 84.

The chevron in the learned-angle fixture became a herringbone whatever
angles were learned, so a cross replaces it; the learned-angle and Strip
mixed nests are re-pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 23:39:11 -05:00
ajandClaude Opus 5.5 f62f73533e feat(fill): tile pair grids into narrow leftover strips
Fill tiled 8" gussets on a 16.85 x 60 strip as seven pairs in a 10.35"
column (14) and left a strip too narrow for a gusset square on. Sliding
each pair along its hypotenuse so six span the height narrows the column
to 8.75" and leaves a strip that holds eight more turned 45 degrees (20),
as laid out by hand.

- The pair remnant filler tries strips down to the part's narrowest hull
  width and tiles single best-fit pairs as a grid there, taking the grid
  only when it places more than the rows of pairs.
- After the pair filler's early exit it also tries skipped candidates
  whose grid plus an optimistic leftover-strip estimate could still win,
  keeping one only when it places more parts.
- The optimistic remnant bound counts parts by true area, not bounding
  box, so interlocking parts no longer prune winning candidates.

The mixed Fill nest pin now places 34 instead of 33; the mixed Strip nest
keeps 35 with its L-shapes laid out differently. Both pass the layout check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:24:49 -05:00
ajandClaude Opus 5.5 5adcddc748 feat(fill): refill the outer row's end to fit one more part
After the fill strategies, the winning layout's outermost row on each side
is pushed into the edge slack, slid toward one end, and copies of its end
part are slid in from the freed end. Lifting a staggered hexagon row straight
over the one below opens a notch where one more nests: a 7" hex cover on a
60 x 120 sheet now fills 181 instead of 180. The policy's comparer must
prefer the result and the requested quantity caps it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 21:46:03 -05:00
ajandClaude Opus 5.5 bcbcf1e611 perf(fill): cut a cold Lattice fill from ~300 s to ~160 s with identical layouts
The first fill of a new drawing was blamed on the best-fit pair search, but on the
reported nest that search takes ~3.4 s. The time goes to FillLinear.FillGrid, run
thousands of times by the Pairs, Row/Column and Linear strategies: pattern copy-distance
slides and the grid overlap check. Every change keeps the output bit for bit (part
poses compared on the benchmark nest; best-fit results unchanged).

- ConvexDecomposition.Triangulate: unlink clipped vertices from a circular list instead
  of List.RemoveAt, and remember why a vertex failed the ear test (same neighbors, same
  blocking vertex still present) instead of retesting it after every ear. Compared
  bitwise against a frozen pre-change copy, including the blocker-clipped case.
- Collision: skip triangles whose box misses the other triangulation's union box (exact:
  min/max and monotone subtraction), reuse each triangle's open vertices, and let the
  overlap-only path stop at its first region when there are no holes. Check still
  returns every region.
- SpatialQuery.ExtractEntityVertices presizes its set; FillLinear extracts each entity
  list's vertices once per copy-distance search rather than once per pair.
- StripeFiller converges its stripe angles and tiles its grids in parallel. Dedup claims,
  cached remnant fills, ranking and progress stay in candidate order, and grids are tiled
  only for configurations a claim can still win (GridDedup.Contains), so results and
  reports do not depend on the thread count.
- LinearFillStrategy fills its angles in parallel through
  FillHelpers.BestOverAnglesInParallel; angle results, ranking and progress stay in order.

Benchmark (one drawing, 60x120, 0.25 spacing, cleared plate, interleaved runs):
FillItem Lattice 291/317 s -> 164/153 s, 132 parts both; PairFiller alone 48 s -> 38-42 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:34:31 -05:00
ajandClaude Opus 5.5 9b643aaec9 feat(engine): refill the pair grid's leftover strip with rows of pairs
The pair filler topped its grid off with single parts at 0 or 90 degrees,
so a narrow strip beside the grid held half of what it could: on a 60x120
sheet of N26-29X parts the plate fill gave 132, while deleting the last
column and filling the strip gave 136. The strip is now also laid out the
way the Row strategy lays one, rows of best-fit pairs turned so whole pairs
span it, and that layout replaces the singles when it places more.

Each strip size is laid out once per fill and skipped when the singles
already fill it by area. On the benchmark nest the plate fill now reaches
136 with the hand-made layout's rotations, about 17% slower (156 s vs
133 s with best fits cached). The pinned L-shape fills gain a part (52 to
53). The auto-nest problem-nest corpus keeps its baseline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:15:36 -05:00
ajandClaude Opus 5.5 dd0c31271a fix(push): let a part push away from a neighbor it touches at the spacing
A part placed exactly one part spacing from its neighbor touches the
neighbor's offset contour. PEP drawings round arc centers to six decimals,
so their offset contours carry hairline kinks and joint gaps where the parts
touch; the slide kernel then classifies that contact as blocking in every
direction, and Push moved the part 0 even straight away from the neighbor.

An obstacle whose bounds end at or behind the moving part's trailing side
can only fall further behind, so Push and PushBoundingBox now skip it
(within Tolerance.Epsilon) instead of only when strictly behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 20:37:53 -05:00
ajandClaude Opus 5.5 0547af2ff6 fix(nudge): always move the full calculated step
Interlocked rows, such as staggered triangles, cannot slide straight past
their neighbors, so capping the step with a push left them unable to
pattern down. The calculated step now moves the full distance like a fixed
nudge, even off the plate or onto a part; the layout check reports either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:42:53 -05:00
ajandClaude Opus 5.5 d98bee87ec feat(nudge): step the selection by a calculated distance
Set Offset Increment gains a "Calculated distance" option. With it on, the
arrow keys move the selection by its own size plus the part spacing,
measured on its geometry like a fill's copy distance, so it lands tight
without a push back. The move stops early at an edge, clamp or part, and a
clone preview steps clear of the copy just placed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:21:56 -05:00
ajandClaude Opus 5.5 8700ae187a feat(expand-spacing): keep rows that share a pitch but stack unevenly
A single-drawing selection that fits no one lattice, such as staggered rows
topped by straight ones, now counts as a pattern when its rows share one
pitch along X (or Y). Each row stretches rigidly instead of the selection
falling back to the slow pair search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:44:12 -05:00
ajandClaude Opus 5.5 7f718f222c feat(expand-spacing): stretch repeating patterns instead of pushing pairs apart
A selection that repeats on a lattice (a fill) now has its lattice scaled
along X, then Y, as far as the work area and clearance floors allow, so the
pattern survives. Multi-part motifs stay rigid, no selected gap closes below
the smallest entry gap, and parts never pass through fixed material. Other
selections keep the pair search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 21:12:22 -05:00
ajandClaude Opus 5.5 12819198f9 feat(fill): search every spiral combination in rectangle best fit
The spiral used only the least-waste rows and columns. Those often make
one pair of blocks the larger on both axes, so it overlapped itself and
placed nothing, leaving Fill at two blocks. It now also tries every
four-block combination and takes one that holds more parts than both the
least-waste spiral and the two-block layout, so ties keep today's layout.

A 9 x 6.5 part on a 60 x 120 sheet goes from 109 to 112.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:42:25 -05:00
ajandClaude Opus 5.5 4b2aba83bb feat(fill): keep interlocked pairs whole and place leftovers as pairs
A quantity-limited fill trimmed the parts farthest along the trim axis,
which could take one half from each of two pairs. TrimToCount now finds
interlocked pairs and keeps or drops them whole (from d1d68a91), so the
spare space stays one usable cell.

After a fill, leftovers now run in two orders, pack-first and pairs-first,
and the strategy's comparer keeps the better whole layout, so its ranking
of this step cannot get worse. Even quantities go in as best-fit pairs,
may turn a quarter when unrestricted, are tried in every remnant and are
ranked with the layout around them. Each placed pair slides left and down
against true outlines to take the space its bounding box could not.

On compare.nest Vertical Remnant goes from 6665.1 to 5862.6 and Lattice
from 7200 to 5915.8. Nine characterization pins change from the trim with
counts unchanged; the fill pins now also check layout validity. Vertical
Remnant's golden layout shortens from 28 to 26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 21:17:15 -05:00
ajandClaude Opus 5.5 970d2442d5 fix(expand): prune clearance checks and let wide gaps close to the target
Expand Spacing measured every ring pair of every part pair for each trial
move, so a packed layout of parts with holes took minutes per spacing
probe. Skip ring pairs, far part pairs and off-path parts whose bounds
already settle the answer.

Every pair was also held at its full starting gap, so parts could not
move toward anything and most layouts stayed at their entry spacing.
Part pairs may now close to the spacing being tried, never below a
smaller starting gap. Cut-offs and clamps keep their full entry gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:59:33 -04:00
ajandClaude Opus 5.5 4d55e11318 style: use one spelling across the codebase
The repo mixed variants such as center/centre and color/colour, so a
name could be spelled either way and searches missed half the hits.
Standardize on the US spelling, matching .NET's own APIs (Color, Center).

Wire values owned by other systems keep their spelling: the material
ledger's "Cancelled" hold status and GitHub's "cancelled" job status.

No behavior change beyond the spelling of a few status messages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:11:30 -04:00
ajandClaude Opus 5.5 b550640ef0 refactor: merge duplicated geometry, settings-file and form helpers
Roslyn Bridge flagged these as copy-paste clusters; each pair now shares
one implementation so a fix lands everywhere at once:

- Shape.OffsetInward/OffsetOutward differed only in the target winding.
- BestFitFinder kept its own copy of SpatialQuery's arc-extreme points.
- SplitDrawingForm rebuilt DrawingSplitter's clip-region grid for preview.
- GeometryOptimizer's collinear/coradial finders (one misspelled
  GetCoradialArs) become one generic FindRelated.
- Five settings files repeated the write-with-retry loop, and two also
  repeated the unit parser and unreadable-file filter (SettingsFile).
- ClampsForm and SupportGridForm repeated their row/box builders.
- Vertical/HorizontalRemnantComparer share a RemnantComparer base.

Behavior is unchanged except that Engine/NestStorage settings loads now
also tolerate NotSupported/Security exceptions, matching the other
settings files' never-throw contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:47 -04:00
ajandClaude Opus 5.5 e76864b489 fix(engine): nest alike on every machine by fixing PairFiller's batches
FillItem_WithClampZones has flipped between two digests because each was
correct for the machine that pinned it. PairFiller sized its parallel
batches from Environment.ProcessorCount, and a batch shares the best count
and work area from before it, so core count decides which candidates are
tried. With 10+ logical processors candidate 9 runs before the area shrinks
and its 46-part layout wins for every comparer (9BF5FC7FC47364A9); with 8 or
fewer the area has shrunk, it can only reach 45, and Fill/Strip and Vertical
Remnant settle on different 45-part ties. 40 of the parts are kept either
way. So the same nest differed between shop PCs, and CI failed whenever the
digests were pinned on a 12-thread desktop.

Fix the batch at 8, which keeps the pins CI has always produced. Also claim
GridDedup keys in candidate order before the parallel loop: the claim was a
race inside a batch, seen as one candidate returning 42 parts and then 33
in the same process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:16:05 -04:00
ajandClaude Opus 5.5 0719476618 feat(clamps): give clamps their own clearance from parts
A clamp rail can now carry its own Clearance, the gap parts keep from
every clamp footprint, independent of part spacing. Unset, it keeps the
sheet's bottom edge spacing as before. Clamp blocks drawn under the
sheet can use close to zero so the laser only stays off them.

Plate.ClampClearance and NestPlateStock.ClampClearance resolve it once;
the engines, interactive fill/pack/push, the layout check, Expander walls
and automatic cut-off placement all read it. The interactive push now
holds clamps at their square clearance keep-out (the layout check's
shape) instead of the part spacing, so parts no longer tuck into its
corners; the clamped fill characterization digests are re-pinned after
checking the new layouts validate.

Persisted as an optional `clearance` on the .nest clamp rail and the
machine clamp settings; edited in Nest > Clamps and the machine's
Clamps tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:25:27 -04:00
ajandClaude Opus 5.5 35359906fe feat(fill): add tilted parts beside a layout whose leftover strip is too narrow upright
Fill only tried 0 and 90 degrees for parts classified as rectangles, so a long
part filling a 60 x 120 plate four high at 90 degrees left a 55 x 59 strip empty
although two more fit tilted about 33 degrees (a reported nest: 4 placed, 6 fit).
The second tilted part only fits with its corner tucked beside the grid, in the
strip the grid's slack leaves, so a plain rectangular remnant fill finds one.

TiltedRemnant runs after the other strategies. It moves the best layout flush to
one end so leftover room becomes one remnant, and to either end of the other axis
so its slack forms one strip; then tiles the part at the most upright angles that
fit the remnant's height and keeps only copies at least the part spacing from the
layout. It is skipped when the part fits the remnant upright, for circles, for an
explicit rotation range and once the requested quantity is placed.

Fill benchmark (--parallel 1): the synthetic set is unchanged (all valid, same
cost and plates). The reported part needs 1 sheet instead of 2 for 6 parts and
2 instead of 3 for 12.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:49:59 -04:00
ajandClaude Opus 5.5 859d10b31d feat(engine): offer a box layout before best-fit pairs in Fill
Interactive Fill ran the best-fit pair phase first. The first pair
computation for a drawing can take about a minute, and until it finished
the progress dialog showed no layout, Accept stayed disabled and the
status text did not change.

The fill now packs the part boxes (RectBestFit) before the pair phase and
reports that layout, so Accept is available at once. The layout is only a
fallback: strategies still run in their usual order, and a completed fill
picks the same winner as before. While the pairs are computed, the detail
line reads "Computing best-fit pairs (may take a minute)...". Accept or
Stop now stops the wait. The computation keeps running in the background
and is cached for the next fill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 12:49:48 -04:00
ajandClaude Opus 5.5 0186ccfbff feat(nest): keep parts out of tabbed cutouts
A tabbed cutout's slug stays attached while the sheet is cut, but nesting
saw a closed hole and could place parts inside it. Nesting and layout paths
now read material through NestingMaterial.ForNesting, which drops closed
contours with tab spans so the slug counts as part. That covers part
outlines and collision lines, the cutout router, fill expansion, best-fit,
NestLayoutCheck and the GPU bitmaps. Area, overlap diagnostics and cutting
keep the hole, and programs without tabs read exactly as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:14:22 -04:00
aj 6424512e8c fix(expand): distinguish open-path material entry from bounds 2026-10-13 22:34:21 -04:00
aj 27f8c9799b fix(expand): measure open cutoff segments and refuse uncertain curved gaps 2026-10-13 22:11:12 -04:00
aj 775ece5d21 fix(expand): certify disk and cutoff paths and report invalid input 2026-10-13 21:49:28 -04:00
aj cad1083287 test(expand): bound pinned relaxation passes 2026-10-13 21:31:57 -04:00
aj 4183af4142 fix(expand): retain convex exits and report tiny clamp overlap 2026-10-13 21:31:57 -04:00
aj 73be301ebf test(expand): verify cancellation after scratch movement 2026-10-13 21:31:57 -04:00
aj fdc6f074ee fix(expand): distinguish part blockers from plate edges 2026-10-13 21:31:57 -04:00
aj dc4b07ddf5 fix(expand): separate parts touching cutout walls 2026-10-13 21:31:57 -04:00
aj 6c76617905 fix(expand): clip translations against material along their path 2026-10-13 21:31:57 -04:00
aj 43e08965b9 fix(expand): preserve entry clearances and retry blocked pairs 2026-10-13 21:31:57 -04:00
aj 74544c689a fix(clamps): preserve interactive clearance and group integrity 2026-10-13 18:15:49 -04:00
aj 3e58a9e93f feat(engine): respect clamp zones in interactive placement and diagnostics 2026-10-13 18:15:49 -04:00
aj 8628c69098 fix(ui): settle shift-click cloned parts with repeated directional pushes 2026-10-08 18:57:39 -04:00
aj 32db086384 perf(irregular): cap surplus rows in private stripe fills 2026-10-05 09:03:49 -04:00
aj 694d4b28cc refactor(geometry): move maximal-rectangle search out of RemnantFinder
The histogram search over a free/blocked cell grid never depended on the
obstacles being boxes. Move it to OpenNest.Core as MaximalRectangles.FromGrid
so other callers can build their own grids; RemnantFinder keeps building its
obstacle grid and calls it. No behavior change.
2026-10-01 07:28:13 -04:00
aj 5e9eaf8a4c perf(fill): reuse validation for an unchanged row
Skip FillGrid's Step 2 overlap check only when Step 1 checked this exact
row clean and the perpendicular tiling appended zero parts, so gridResult
still holds the same Part objects in the same order and poses. The local
rowIsVerified flag is cleared by every Step 1 skip or bbox fallback, and
any nonzero append (including partial-copy parts) keeps the eager check.

Debug work assertions pin the reduced overlap work (horizontal stripe
8->4 exact calls, 4->2 preparations, 16->8 triangulations; vertical
stripe 36->18, 4->2, 38->19) and keep single-seed, Horizontal full-grid,
partial-only and PerpOnly work at base; the invalid overlapping-seed
control pins both fallback stages plus the retained exact-call total.
Three mutants (forced eager check, dropped flag, complete-rows-only)
are caught by their designated controls. Serial whole-job layout is
byte-identical to the frozen 62b5a8d0 oracle before and after.
2026-09-30 17:01:11 -04:00
aj 3cab90747a feat(expand): plateview spacing expander
Grows part-to-part spacing of a selected group with the work area and
non-selected parts as hard boundaries. Doubling + bisection search over
the target spacing; Gauss-Seidel straight-line relaxation with anchor
mover policy (later-index selected part moves, first selection never).
Overlapping input is separated along penetration MTVs instead of being
rejected. Cancel/failure never mutates part positions. Clearance gains
BoundaryDistance for ring-pair gaps (part-in-cutout legality).
2026-09-28 03:05:45 -04:00
aj 2b78fb3a75 fix(geometry): resolve blocking contacts across directional slide paths 2026-09-27 23:24:31 -04:00
aj a27290a29c perf(fill): prepare overlap polygons once per check
Both HasOverlappingParts loops rebuilt each part's polygon from its
Program on every pair. PartOverlapChecker prepares each distinct Program
(reference identity) once and each part's world polygon once per call,
then uses the overlap-only Collision.HasOverlap. Loop order, bounding-box
prefilter, early exit and returned indices are unchanged; Part.Intersects
shares the material/polygon recipe and still returns crossing points.

Verification:
- Frozen LegacyPartOverlap differential (original Intersects and both
  loops): verdicts, indices and world polygons bit-identical across fill
  grids, patterns, touching/epsilon gaps, scribe/rapid/empty programs.
- Debug OverlapPolygonPreparations: 246 -> 1 and 64 -> 2 per check.
- Corpus job (169 parts, --engines Default --parallel 1, with 1a):
  median 18,885 -> 13,464 ms over 4+4 alternating runs, identical
  outcomes; serialized layout byte-identical to the base.
2026-09-27 13:34:04 -04:00
aj 0df2587cf2 perf(fill): reuse offset geometry for translated copies
FillLinear re-prepared offset perimeter geometry (ConvertProgram ->
ShapeProfile -> OffsetOutward) for every part it measured, although
tiled copies share one Program and differ only by Location. A CPU
profile of a 169-part Default job put 62% of wall time there.

Prepare each distinct Program (reference identity) once per public
Fill/FillRow call in local frame, then clone and translate for each
location. The cache is created per call and passed down privately
because FillHelpers.FillPattern calls Fill concurrently on one
instance. PartGeometry gains a local-frame Program overload that the
Part overload now delegates to.

Evaluation order, lazy preparation, fallbacks and tiling are
unchanged. Differential tests against a frozen copy of the previous
FillLinear check bitwise equality, including concurrent calls; Debug
work tests pin preparation counts. With the thread pool capped at one
worker, before/after whole-job layouts are byte-identical. The
Default corpus job median drops from 40,715 to 18,810 ms.
2026-09-26 20:24:34 -04:00
aj 1e8e532063 perf(fill): skip feature extraction without an angle model 2026-09-26 10:02:15 -04:00
aj 4553f8afad perf(fill): avoid redundant bounds recomputation 2026-09-25 21:45:03 -04:00
aj 6efa6b1117 perf(fill): remove discarded extents pitch geometry 2026-09-25 19:52:19 -04:00