FillEndOdd and FillEndEven read Box.Width as the X span, though it is
the Y span, so non-square sheets got the wrong pitch. They also started
centers at the bin corner, leaving circles a radius outside it.
FillEndEven now tries both stripe directions, as FillEndOdd does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Engine tests drew the same rectangle, square, L-shape and arc part in
private helpers across the Expander, golden-layout, fill-stack and
fixed-strategy tests. They now use TestDrawingFactory; files whose
helper had its own default size keep a one-line wrapper for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Private copies of tolerant box containment, box union, vector rotation and
the cutout proposals' coordinate-range check now use one helper each:
BoundingBox.IsWithin and BoundingBox.Union, Vector.Rotate, and
CutoutCoordinates.WithinRange. Tests pin the helpers bit-for-bit against
frozen copies of the removed bodies. No behavior change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
DefaultUnderATimeLimitReturnsAValidPlanNoWorseThanUnlimited failed on CI
(runs 41219924107 and 41224809174, Engine.Tests job): the fixed 5 s limit
assumed the unlimited Default solve takes well under a second, but it takes
about 4 s locally and longer on a loaded runner. A limit shorter than the
normal Irregular search correctly returns an earlier, worse plan, so the
assertion only holds when the limit covers that search. Use the larger of
5 s and three times the measured unlimited solve.
Red: the old test fails on one CPU shared with three busy loops; the new
one passes there. Full Engine.Tests: 2780 passed, 2 skipped.
Equal-cost plans break ties by the largest edge offcut, from any of the
four edges. A caller that wants the leftover off one way, such as a
shop that keeps only end drops, or a benchmark scoring the used sheet
height, had no way to say so. On the converted Nestbolt jobs the
extended Irregular search then found plans whose larger offcut is a
strip down the side, which Nestbolt scores as worse: b-blaz1-strip
3.601 -> 3.867, b-shirts-strip 2.674 -> 2.995.
NestJobOptions.OffcutCut (a CutOffAxis, default null) counts only the
strips one cut along that axis frees, for salvage credit and the
compactness tie-break alike: Horizontal the full-width bottom and top
strips, Vertical the full-length sides. PlateRemnant.LargestOffcut takes
the axis; NestJobCost, the time limit and clamp-removal recovery pass
it on. Unset, nothing changes.
Benchmark manifests accept "offcutCut": "horizontal" or "vertical".
With horizontal, one CPU, 20 s limit, Default:
b-blaz1-strip 3.934 -> 3.333
b-shirts-strip 2.995 -> 2.610
m-box-deep 1.918 -> 1.276 (Nestbolt 1.251)
Manifests accept only the words horizontal or vertical (any case); a
list such as "horizontal,vertical" is rejected. PlateRemnant rejects an
undefined axis. Fixed strategies keep their own no-salvage tie-break
and ignore the option; desktop, Console and MCP do not offer it yet.
Mutation reds: 14/14 (offcut-mutations.py).
Under a time limit, Irregular often finished in about a second and left
the rest unused: on the converted Nestbolt b-jakobs1 job it used five
sheets where four fit.
NestJobOptions.SearchUntilCancelled asks an engine to keep searching
after its normal search until the token is cancelled.
DeadlineNestingEngine sets it. Irregular then re-runs its six variants
with seeded per-type weights on its placement rule (seeds 1, 2, 3, ...,
each weight within e^+-0.4), reports each plan that beats its best,
then re-plans that plan's tail and reports any gain. It skips this when
the token cannot be cancelled, nobody listens for progress, nothing is
placeable, or the job has one part type, so unlimited solves are
unchanged.
Default now runs Irregular last (Rectangles, Circles, Irregular) so
Circles still runs, and chooses only after every candidate has run, in
tie-break order, so the run order never changes the choice. Unlimited
choices match the old run order {Rectangles, Irregular, Circles} except
when three candidates are near-tied within the 1e-9 comparison
tolerance without being transitively ordered (only on all-round jobs,
where Circles bids).
b-jakobs1 on one CPU, 10 s limit: 5 sheets (4.500) -> 4 (3.751).
DeadlineNestingEngine used CancelAfter, a thread-pool timer. A CPU-bound
Parallel.For keeps pool threads on their own local work, so the timer
callback can run seconds late; pinned to one CPU it waited for the
whole best-fit search (m-sign-3 under a 10 s limit returned at 64.5 s).
A dedicated background thread now waits for the limit, or for the
solve to finish, and cancels; the solve joins it before returning.
A test runs a CPU-bound Parallel.For from a pool thread under a 200 ms
limit and requires the solve to end within 3 s; with CancelAfter it
took about 7 s.
PairCatalog waited for BestFitCache's search without the solve's token,
so a cancelled or time-limited Irregular solve kept running until the
search finished: minutes on one CPU for the many-segment gear, sign and
garment parts of the Nestbolt benchmark (m-public-gear-1 ran 312 s under
a 60 s limit, m-garment-teagan-1 45 min). It now uses the cancellable
overload, which stops the wait and lets the search finish in the
background and fill the cache. The drawing is private to the call or to
the caller, as that overload requires.
A test blocks the pair evaluator and checks Build throws within 5 s of
a 200 ms cancellation; with the old call it waited the full 30 s.
DeadlineNestingEngine wraps any engine with a wall-clock limit (any positive time up to
the cancellation timer's ~49.7 days). An engine that finishes in time
keeps its result, as does a result it returns after the limit. When the
limit cancels it, the best whole-job plan it reported (PlanImproved,
ranked like Default ranks candidates, a tie going to the later plan) is
returned if it passes the layout check; with none the solve fails with
TimeoutException. The caller's own cancellation is never turned into a
result. Engine plate commits are dropped and the returned result's
commits reported once it is known; plans are forwarded only when they
beat every plan shown so far.
Default now runs Rectangles, then Irregular, then Circles, so a search
stopped early already holds a valid plan. Ties still break by candidate
order (Irregular, Rectangles, Circles), so for these three the choice is
unchanged, and when every candidate throws, the first in that order is
still the one rethrown. A test seam takes a run order. Desktop, Console
and MCP do not apply a limit yet; Console can solve a job twice (cutout
trial and plain), which needs one shared deadline.
Mutations killed (10/10): no fallback at the limit, invalid plan
accepted, caller cancellation becomes a result, engine commits
forwarded, a worse later plan replaces the best, a tie keeps the earlier
plan, no upper limit bound, failure rethrown in run order, built-in run
order unchanged, run order ignored.
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>
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>
On a plate with clamps, MaterialsTooClose measured gaps on polygons faceted
at 0.001, which understate an arc gap by more than the 0.0005 slack. A pair
interlocked exactly at the spacing read as too close, so FillGroup discarded
every copy and the fill silently placed nothing. Confirm the gap on
NativeContours, as the layout check and pre-post review already do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0719476 re-pinned the Fill, Strip and Vertical Remnant clamp-zone
characterization digests to 9BF5FC7FC47364A9, but that layout never
reproduced: CI and local Linux runs at that commit and since give the
previous digests unchanged, so Cross-platform tests has failed on every
push since. Clamp clearance defaults to the bottom edge spacing, which
these plates already used, so their layouts were never expected to move.
Checked the produced layouts before restoring them: all four strategies
place 40 parts inside the work area with no NestLayoutCheck violations
against the clamp clearance.
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>
PlateFillService throws for a plate with a sheet outline, but the desktop
swallowed it: Fill (Ctrl+F) caught every exception and showed nothing, and
Fill Area's async void either completed silently or let it escape.
PlateFillService.Refusal(plate) names the reason, and every caller checks
it first: Fill and Fill Area put it in the status bar and add nothing,
Pattern Tile offers a new plate only, the MCP fill/pack tools return it as
an Error result, and the console exits 2 with it unless --autonest is given.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
NestPlateStock carries the outline; its usable material (less obstacles,
eroded by the largest edge spacing) becomes the FrontierPacker's
PackBoundary. The layout check and the runner's candidate validation
reject a part outside the outline or over an obstacle, area-based cost
and the area budget use the material area, salvage credits nothing, and
Rectangles, Circles, FlatBar and the fill strategies place nothing on it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CutoutPackerProposal runs the Irregular engine's FrontierPacker on one
hole, frame-local, with the hole as its boundary and already seated
inserts as obstacles, for all six strategy variants; every pose is
certified with NestLayoutCheck.Clears against the frame, the occupants
and the other poses, and the variant seating the most copies wins, ties
going to the tighter layout. CutoutRouter keeps that proposal over the
lattice-and-NFP one when it seats more copies, or the same number more
compactly, so the pre-pass gets it on every non-round hole.
Two copies of a bar in a frame window, which the lattice never tried and
the sampled NFP search scattered across the window (one at the inner
fit's centre), now stand together against one wall. The test rebuilds
that frame and bar from embedded G-code; the residual-accounting
pipeline tests use a hole that holds one insert so their second copy
stays an ordinary requirement.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The cutout pre-pass offered lattice/NFP inserts only when they had no
cutouts at all, so a strap with 0.25" bolt holes was never tried in a
frame's window. On N26-264 that left both PT11s loose on the sheet and
Rectangles' staggered PT10s won, although both PT11s fit one PT10's end
window. Holes matter only if another part could land in them, so a part
whose every cutout is smaller in area than the job's smallest outline is
now an ordinary insert; proxies and parts with usable holes stay out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FillItem_WithClampZones_PinnedLayout (Fill, Strip, Vertical Remnant) has
failed in the Engine.Tests CI job on every push since it was added in
ccd361d: the pinned digest 9BF5FC7FC47364A9 is not reproducible at that
commit or any later one, locally or on GitHub runners. The engine
deterministically yields 9B3EFF9B72243576 (Fill, Strip) and
039C0D607166D805 (Vertical Remnant) at ccd361d, 36c8de8 and master, so the
later filler consolidation is still layout-neutral for these cases.
Before repinning, all four strategies' layouts were checked: 40 parts, all
inside the work area, bounding boxes clear of both clamps, and no
overlapping parts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DefaultPlateNester, StripPlateNester and RemnantPlateNester differed only
in the filler they built. They are replaced by one internal PlateNester
over a Func<Plate, PlateFiller>, which keeps the old Place exactly:
restricted rotation goes to OrderedPlateNester, and each trial gets a fresh
private plate and filler.
PlacementStrategies maps Fill (and its legacy "Default" alias), Strip and
both Remnants to a filler policy and layout. PlateNesterFactory reads it
case-sensitively and PlateFillService ignores case, as before; each keeps
its own error type and message. Nothing outside the Engine and its tests
used the three public nester types, so they are removed.
No layout changes: all 29 fill-stack pins pass with the pin file untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DefaultPlateFiller becomes PlateFiller(plate, policy, layout); PlateFillerBase
and StripPlateFiller are gone. PlateLayout.FillThenPack is the old
orchestrator path (Fill and both Remnants); PlateLayout.Strips is the old
StripPlateFiller.Nest, and Strip is Fill policy plus Strips layout.
Lifetimes are unchanged: a strip-layout filler runs every Fill, group Fill
and PackArea on a fresh inner filler and gives each strip fresh remnant
fillers, so learned angles and PlateNumber never leak into them. The
comparer, preferred direction, trim axis and angle recording now read the
policy directly. The base class's unsorted two-angle BuildAngles is dropped:
only DefaultPlateFiller's pipeline called BuildAngles, and Strip never ran it.
Layout-neutral: all 29 FillStackCharacterizationTests pins pass unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fill, Vertical Remnant and Horizontal Remnant are now three values of an
internal PlateFillPolicy that DefaultPlateFiller takes (Fill by default),
replacing RemnantPlateFiller and RemnantFillPolicy. The policy holds the
comparer factory, preferred direction, trim axis and angle mode; the
remnant angle mode still replaces the angle builder outright, and the
public FillPolicy that strategies read is unchanged. Layout-neutral: the
fill-stack characterization pins pass unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NestLayoutCheck rejected Fill layouts whose interlocked arcs sat exactly
at the part spacing: its SpacingOutline circumscribes arcs by up to 0.001
each, more than the 0.0005 slack, so facing arcs overlapped. The runner's
placement validator measured inscribed 0.001 polygons instead and so
accepted gaps up to ~0.002 under the slack.
Both now find candidates with SpacingOutline and confirm them on
NativeContours, the native contours flattened at a twentieth of the slack
(moved out of PartSpacingReview, which keeps using them). The runner calls
NestLayoutCheck.KeepsSpacing, the same outline, tie rule and
confirmation, so the two validators accept exactly the same pairs.
Clears stays unconfirmed and stricter for engine certification. No
tolerance changed.
FixedStrategyEngine_IrregularJob pins now assert LayoutAssert.Valid with
unchanged digests; the NativeU 70-part grid's 28 reports were the same
cause and its test now requires none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BestFitFinder gathered each rotation strategy's candidates, and PairEvaluator
gathered evaluated results, in ConcurrentBags, so their order depended on which
worker finished first. The stable area sort then broke equal-area ties in that
arbitrary order, and fills, which try candidates in order and keep the first of
equal layouts, could nest the same parts differently between runs.
Both now fill index-addressed slots: candidates keep rotation-angle and
slide-offset order, results keep candidate order, and ties break the same way
every run. The candidates produced and their scores are unchanged.
The Strip whole-job pin in FillStackCharacterizationTests is re-pinned
(A66602F707290A36 -> FD03A4529D37E32C). That job ties between best-fit
pairs; the old pin recorded whichever tied pair the bag happened to return
first. The new layout places the same 26 parts, passes LayoutAssert.Valid
and gave the same digest in five separate test processes. The other 28
pins are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StripPlateNester sent every request to the strip fills, which try two
classification angles and ignore rotation bounds. With a fixed or bounded
rotation at quantity above one they proposed forbidden poses, the job
validator threw "Candidate rotation is not allowed for the requirement."
and the whole Strip job failed. Strip now uses the same guard as
DefaultPlateNester and hands such plates to OrderedPlateNester.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Characterization digests for the interactive PlateFillService surface and
the fixed-strategy whole-job engines on L-shaped, notched, arc, bar and
chevron parts: single-item fills, whole-plate nests (including one where
learned angles change the layout, guarded by a full-sweep comparison),
fills beside clamp zones, and one job per Fill/Strip/remnant engine.
They let the planned filler/nester consolidation be shown layout-neutral.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every whole-job engine chooses among the offered stock sizes, and on the
synthetic benchmark nests Default matched or beat StockLadder on all 18
runs (saved sizes, a six-size pool, with and without salvage credit),
costing 30-40% less in total with mixed sizes. Its sheet-swap step never
produced a win over Irregular's tail re-plan.
The registry maps "StockLadder" to Default so saved selections, scripts
and API requests keep working. OrderedPlateNester stays: Fill and the
remnant strategies use it for restricted rotations, and its golden layout
is now pinned through NestJobRunner with the parts requested largest
first, as StockLadder requested them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto Nest's Stock Options gets a Load from ledger button that reads the nest
material's free sheets and drops from MaterialLedger's API-key-protected
GET /api/stock/opennest. Ledger rows are request-local: the engine sees them as
ordinary NestPlateStock rows with ledger:{n} ids and finite quantities, and they
are never written to Nest.PlateOptions.
- OpenNest.Data/MaterialLedger: settings file, read-only client (X-Api-Key,
5 s timeout, closed LedgerStockResult set) and LedgerStockMapper (bundles and
sheets one row each, same-size drops grouped with ordered item codes, unknown
shapes and too-small items skipped, inch/mm conversion).
- Options: MaterialLedger URL and API key (DPAPI, CurrentUser) with Test.
- Auto Nest grid: Use, Qty and Source columns for every row; PlateOption gains
request-local Quantity and Id, which NestStockBuilder passes to the engine.
Ledger rows score by area; hand-typed costs stay saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without salvage credit (desktop Auto Nest on a single plate size) every
complete one-sheet layout costs the same, so engines kept whichever layout
came first. N26-23S nested 27.86" long when turning two parts fits it in
21.3".
NestJobCost.CompactnessOffcut measures the largest full-width/full-length
edge offcut as salvage does, whatever the salvage rate. Default and Circles
(CandidateScore), Rectangles and Circles sheet choice (SheetByStock) and
Irregular sheet and plan ranking use it only after cost, so no cost changes.
Sheet-level keys round to 1e-6 to keep ties platform-independent.
Rectangles layout digests are re-captured: each changed fixture keeps its
sheets, parts and cost and leaves a larger offcut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Roslyn duplicate scans found the same code copied across layers:
- Data: the three browse sessions share BrowseSession<TPage, TSort>;
the remote clients share NestServerHttp (JSON options, If-Match,
error text).
- WinForms: five paged lists share BrowsePager for loading text,
navigation, sort glyphs and failure messages. The Add Drawings to
Release, Drawing Library and set browser dialogs now dispose their
browse sessions.
- Server: NestQueryRequest reuses DrawingLibraryRequest's helpers;
revision history queries share count and paging binds.
- Engine: RemnantPlateNester delegates to DefaultPlateNester;
CuttingPlanService reuses JointCuttingPlanSearch.Map.
- Core: LineArc and CleanHole lead-ins share LineArcApproach with
unchanged arithmetic order, so output is bit-identical.
- IO: Dxf.GetGeometry overloads share one reader; .nest JSON test
helpers move to NestArchiveJson.
Also fixes the production compiler warnings (CS8632, CS8603, CS0108)
and moves a test off an obsolete forwarding API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
TryImprove kept a swap when the trial run looked fuller than before, but
the trial charges each type's self-nesting gain again even when the run
already holds that type. Trading a placed copy for an identical unplaced
one therefore looked fuller by the gain and repeated forever: FlatBar ran
into the benchmark's 5-minute timeout on tabbed_cog_layout once the
all-edges MBR fix gave one part new rotations (it finished in 7 s before).
The swap is now judged on the merged run, so every kept swap really
leaves the run fuller and the loop terminates. Swaps the old test kept
that do fill the run are still kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Desktop Auto Nest's "Best plan so far" strip now fills in for every built-in
whole-job engine except FlatBar, not only Irregular.
- Rectangles makes one sheet-by-sheet run, so it reports the finished job as
its one plan, then replays its plate commits (token checked as before).
- Circles reports when a stock-preference run or a tail re-plan beats its
best result so far.
- Default scores each candidate plan as it ranks results (valid, fewest
unplaced, lowest cost, candidate order) and reports it only when it beats
every plan already shown, so a later candidate never shows a worse plan.
The chosen result is always the last plan reported.
- Shared ReportPlan/SameLayout helpers on CandidateProgress; Irregular uses
them too. Reporting never changes the search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto Nest no longer draws parts on the plate view before they are kept:
its result is a set of sheets, possibly of several sizes, not parts on the
current plate. Ghost parts stay with interactive Fill and remnant tools.
- NestJobStage.PlanImproved carries an engine's best whole-job plan so far;
Irregular reports one whenever a variant or the tail re-plan beats it.
- NestPipeline adds PlanSheets: the plan on detached drawings built from
the job geometry (with the caller's names and colors), so previews never
touch caller drawings or quantities.
- SheetStripControl draws every sheet side by side at one scale, repeats
once with their count, captioned with size, parts and utilization.
- The progress window shows "Best plan so far"; a review window shows the
final sheets, totals, cost summary and unplaced parts before commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NestProgress gains PreviewSheet, a detached plate for the stock the preview
parts lie on; Irregular builds one per stock. PlateView draws it (size, edge
spacing, clamps) in place of the plate while the preview is shown and drops
it whenever the preview is cleared or the plate changes. Auto Nest zooms out
when a trial sheet falls outside what it has already shown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frontier packer reports its placements after each choice; the engine
turns them into preview parts at most four times a second and sends them as
NestProgress.BestParts with the stock work area, which desktop Auto Nest
already draws on the plate view. Reporting only reads finished placements,
so layouts are unchanged with or without a progress sink.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every Auto Nest run, and every Default-engine run, recomputed each part's
best-fit pairs: job snapshots are fresh objects and BestFitCache keyed
its entries on Drawing reference, so the Irregular engine paid
PairCatalog.Build (about half of a 118-part solve) on every run of the
same drawings.
BestFitCache now keeps a process-wide, bounded (512 sets, least recently
used first) pool of unfiltered candidate sets keyed by the exact content
of a PartGeometrySnapshot (mode and every motion value, bit for bit;
PartGeometrySnapshot.ContentComparer) and the spacing. Every drawing
DrawingJobMapper.CreateDrawing reconstructs from a snapshot declares
that geometry (BestFitCache.ShareByGeometry), so the Irregular pairs and
blocks, the cutout lattice and the Fill/Default job paths all read and
fill the pool. Candidates depend on the program geometry and spacing
alone, so a pooled set is the set the search would produce: benchmark
layouts, costs and placements are unchanged on the synthetic nests (all
engines) and on the 67-drawing, 118-part job. Failed searches are not
pooled, Invalidate leaves the pool alone, Clear empties it, and a
drawing whose Program is replaced stops vouching for its geometry.
Same 118-part job solved twice in one benchmark process (--parallel 1):
the second solve now takes 13.1 s after a 33.0 s first; before, 35.6 s
after 41.8 s.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 67-drawing, 118-part job took about six minutes through the desktop
Auto Nest path and was cancelled before any layout appeared. Almost all
of it was the cutout pre-pass, the rest the Irregular engine.
Cutout pre-pass (317 s -> 8 s on that job, same bundles and poses):
- The lattice and NFP searches prepare the frame's and the occupants'
validation outlines once per search (NestLayoutCheck.Prepare) instead
of re-outlining and re-triangulating the frame at every candidate, and
test candidates against the small occupants before the frame. Answers
match the pairwise check exactly, including its leftmost-inflated rule.
- A hole's proposal is replayed for the same frame, hole and offered
inserts: automatic-rotation inserts ask the same question at every
frame angle.
Irregular engine (40 s -> 15 s, benchmark 42 s -> 19 s, same layouts):
- Concave no-fit polygons are built as the union of pairwise convex sums
of each footprint's convex pieces (ear clipping plus Hertel-Mehlhorn
merging on the Clipper lattice) instead of n*m edge-pair quads; a
footprint that cannot be decomposed keeps the old sweep.
- Pair and block catalogs share one private drawing per part geometry,
owned by the engine instance, so each part's best-fit search runs once
per instance and across a pipeline's trial and ordinary solves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dropping the parts that land on a clamp suits small parts, which fill the
gaps between clamps. Large parts often do better if the fill area stops
short of the clamps. The rows still fit, and no part is lost on a clamp.
When every clamp in the fill area lies on one edge, interactive Fill now
also fills the area with that edge moved just past the clamp keep-outs,
as a larger edge spacing would. It keeps whichever layout the strategy's
comparer ranks better, and a tie keeps the dropped-parts layout. Clamps
on more than one edge skip the comparison. The second fill reuses the
cached best-fit pairs. While it runs, a preview counts as the new best
only when it beats the first layout, so the preview never gets worse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interactive Fill on a plate with clamps used to split the plate into
clamp-free rectangles and fill each one separately. It showed no progress,
so Accept did nothing until the end, and Stop discarded the result. Packing
around the clamps also tends to give a worse layout than losing the few
parts that land on them.
FillItem and FillGroup now fill the area as if there were no clamps. They
then drop each part that enters a clamp keep-out or sits too close to an
existing part, using the same check as before. For a multi-part group, the
whole copy is dropped when any member fails. Progress previews are filtered
by the same check, so a layout accepted early is clamp-clear. A quantity
request that loses copies asks for that many more, up to two more times.
Pack and whole-plate Nest still pack only into the clamp-free areas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Circles packs every part as its minimum enclosing circle at centers tangent
to two of the work-area walls, placed discs and clamp keep-outs, bottom-left
and left-bottom, with hexagonal and square lattice seeds. It bids only when
every part fills at least 0.8 of its enclosing circle and otherwise returns
everything unplaced at once. Default runs it as a third candidate.
Rectangles' sheet-by-sheet stock loop moves to NestingEngines/SheetByStock
(Rectangles output unchanged) and Default's candidate scoring and commit
forwarding become shared helpers. Circles also runs the whole job preferring
each offered stock and keeps the best result. Contract tests take shapes an
engine can override.
Synthetic round jobs, salvage-credited: Circles wins or ties all six with
valid layouts; Default cost falls about 4% versus Irregular alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cutout prepass now bundles frames smallest first, so a larger frame's
round cutout can take a ring part or an earlier bundle as one insert through
the circle packer: a flange holds a washer that holds a smaller washer that
holds a slug. Chains stop at four parts deep. Lattice and NFP proposals never
take these inserts, since they could seat a part in a ring's bore.
Expansion and released demand recurse, so placed order cuts innermost first
at every level and an unplaced outer frame returns its whole chain to demand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add CutoutCirclePacker, a circle-in-circle proposal for the cutout
router. When a cutout is one circle and every insert fills at least 0.8
of its enclosing circle, it packs the inserts largest first at centers
tangent to the wall or placed discs, trying a center-out pass (a single
insert sits concentric) and a wall-first pass (optimal counts for up to
nine equal discs). Every pose passes NestLayoutCheck.
The router runs it first and skips the lattice and NFP searches when it
places all demand; otherwise the higher count wins and existing
proposals keep ties. On synthetic disc jobs the prepass drops from
minutes to seconds and places as many or more inserts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item.Create builds the circle packer's view of a part: the minimum
enclosing circle of its outward-flattened perimeter (padded by the
layout check's flattening allowance), its fill ratio, the offset back
to the part origin, and the largest disc clear of material inside its
cutouts. Gears and sprockets pack by their tip circle around their
bore; the fill ratio lets a future engine decide which jobs to bid on
instead of a yes/no round test.
Adds MinimumEnclosingCircle (Welzl, repeatable order) to Core geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Staggered stripes were spaced only so adjacent stripes touched
diagonally. When the in-stripe pitch stretched past sqrt(3)*D, stripes
two apart, which sit directly over each other, came closer than a
diameter. Clamp the stripe spacing to at least a radius in both fills.
FillEndOdd also divided by zero when a single circle fit per stripe;
it now stacks those stripes a diameter apart. FillEndEven now clones
the template item, so placed circles keep its radius instead of zero.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reads each stock as a bar and cuts parts in one row along its length, packing
part lengths with a port of the CutList saw cut-list packer (first-fit
decreasing with swap optimization, exhaustive search for small jobs) plus a
best-fill subset-sum candidate. Each part is packed at its length less its
measured self-nesting gain, and a slide-left compaction pass nests shaped ends
(turning alternate mitred parts) and refills the freed length. Every slid pose
is confirmed with NestLayoutCheck.Clears.
On five synthetic flat-bar jobs every layout is valid and complete; total cost
33696 vs Rectangles 36816 and Default 34080.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>