Commit Graph
65 Commits
Author SHA1 Message Date
aj 575412a464 feat(cutouts): propose certified single inserts with inner-fit geometry 2026-10-09 16:59:11 -04:00
aj 5af44b251c feat(engine): make Default choose between Irregular and Rectangles per job
Default is the engine every front end uses when none is named. It now runs
Irregular, then Rectangles, checks both layouts with NestLayoutCheck and keeps
the best: valid first, then fewest unplaced parts, then lowest salvage-credited
cost; ties keep Irregular. A candidate that throws or returns nothing is
skipped, cancellation stops the search, and only the chosen layout's plate
commits are reported. Neither engine wins every job in the lane benchmarks,
and Rectangles adds little time while also covering Irregular's invalid
layouts.

The registry lists Default first and no longer maps the name to Fill; fill
strategy callers still read Default as Fill. A future circle/ring engine joins
as another candidate.
2026-10-06 12:26:10 -04:00
aj 0a1d021bf7 refactor(engine): rename the Default engine and fill strategy to Fill
The multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder) is
no longer meant to be the engine used by default, so it gets a name for what
it does. The registry lists it as Fill and maps the old name Default to it;
PlateFillService, PlateNesterFactory and NestJobOptions use Fill, and every
fill-strategy caller still accepts Default. Console --autonest now validates
engine names through NestingEngineRegistry.ResolveName so renamed names work.

Layouts are unchanged: Default and Fill resolve to the same fillers and the
golden layouts pass under the new name.
2026-10-06 12:11:03 -04:00
aj 6b4078aeb2 style(tests): apply dotnet format to files touched by the Fill rename 2026-10-06 12:10:42 -04:00
ajandClaude Sonnet 5 eda4a846f0 fix(irregular): charge nested block-fill cost against the work budget
BlockCatalog's PrepareBlocks pre-pass ran a full independent NFP-based
pack (PrivatePlateFill.Run) for up to 8 attempts per part type, but that
cost never touched the engine's WorkCounter/WorkBudget. For jobs with
several part types at double-digit quantities, these uncounted nested
solves could burn the entire 5-minute timeout inside the first sheet of
the first strategy variant, so Solve() never returned and nothing was
ever committed.

Charge quantity^2 * outline vertex count against the shared counter
before each nested fill, and stop issuing new block proposals once the
budget is exhausted, so the existing effort governor actually sees and
bounds this cost.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-05 09:29:46 -04:00
aj 7451596047 test(cutouts): keep the repeated-call determinism test, skipped on the open Fill defect
Restore RepeatedCallsReturnIdenticalPoses for a 1 x 1 square and a 1.5 x 0.75 rectangle.
Both cases fail intermittently because Default Fill can return different, equally scored
lattices for identical calls (Project Memory opennest c98c21bd), so they are skipped with
that reason until the defect is fixed rather than removed.
2026-10-04 20:07:52 -04:00
aj 15cca0efd5 feat(cutouts): fill a cutout with a shifted Fill lattice
CutoutLatticeFill fills one closed cutout of a frame part with copies of one part, the
first piece of the cutouts-first pass. It runs Default Fill over the cutout's bounds
plus one part step on every side, shifts the lattice over a (2n+1)^2 grid of offsets
up to half a step each way, and at each offset keeps the copies whose reference point
lies in the part's inner-fit region of the inscribed cutout (part circumscribed and
grown by the spacing). The offset keeping the most copies wins; every returned pose
is then certified with NestLayoutCheck.Clears against the frame and the other copies.

20" ring, 10" round cutout, 1" squares, 0.25" spacing: 37 copies, against 25 for a
block sized to the inscribed rectangle and 32 for the unshifted lattice.

Not wired into any engine or pipeline: parts inside cutouts wait on containment-aware
cutting order. Fill can still return different, equally scored lattices between calls
for some parts, so identical results are not yet claimed.
2026-10-04 19:56:05 -04:00
aj c49c387af5 merge: integrate exact-contact rotated rectangle repair
Brings in the independently reviewed exact-contact repair (c7b4ba4,
25755a8, bd5db59): line-only part outlines keep exact analytic bounds,
so an 8x3 part fits rotated on 8.5x3.5 stock at 0.25 spacing, while
curved outlines keep their conservative padding and no validator,
spacing or fit tolerance changes. The held hole-profile and pocket
commits built on bd5db59 stay unmerged.
2026-10-02 20:50:19 -04:00
aj 14a7ce43a7 test: prevent selected-area fill from crossing sheet cutoffs 2026-10-02 07:12:15 -04:00
aj bd5db59e42 fix(irregular): preserve analytic line bounds after polygon cleanup 2026-10-01 20:45:22 -04:00
aj c7b4ba4457 test(irregular): cover exact-contact rotated rectangle placement 2026-10-01 18:44:51 -04:00
aj c01a348243 feat(irregular): offer certified Fill blocks for repeated parts 2026-10-01 16:17:25 -04:00
aj 94e23e4b7a feat(irregular): place best-fit pairs through member free regions 2026-10-01 12:31:38 -04:00
aj 6b6928213d fix(packing): apply work-area slack only at plate boundaries 2026-10-01 08:15:57 -04:00
aj 62fd42346e fix(irregular): prevent overlaps from false NFP gaps 2026-10-01 01:05:07 -04:00
aj 7ce53008f1 fix(rectangles): place parts within the layout check's work-area slack
Parts drawn a few millionths over their sheet's work area (for example a
36.125006 panel on a 36.125 sheet with no edge spacing) pass NestLayoutCheck,
which allows Tolerance.Epsilon of overhang, but the Rectangles engine refused
them and left them unplaced. The engine now allows 90% of that slack: a box
that exceeds the sheet by no more than the allowance packs as exactly the
sheet's size, so it spans the axis and the overhang lands only past the edge.

The check's bounds slack is named NestTolerances.WorkAreaSlack (same value,
no behavior change) so the engine and the test layout assertion share it.

Rectangle-lane benchmark (91 strict + 77 boxable jobs): every layout valid;
4 jobs that left panels unplaced now place them (strict complete 86 -> 88,
boxable complete 74 -> 75, one more boxable job places 2 more parts); no other
job changed.
2026-09-30 22:58:41 -04:00
aj 4b6e131cec style(tests): apply dotnet format to RectanglesNestingEngineTests 2026-09-30 22:58:41 -04:00
aj 2e700df8ed feat(fill): pack leftover parts with maximal rectangles
DefaultPlateFiller.PackArea (Default, Strip and both Remnant strategies)
now packs leftover parts with the shared maximal-rectangles packer instead
of bottom-left corner points. Every fit rule and pick mode is tried; the
layout placing the most parts, then box area, per priority tier wins, and
the strategy's own fill comparer breaks ties so remnant strategies keep
their clear side. PackBottomLeft and PackEngine are removed.

Rectangle-lane benchmark (91 strict + 77 boxable jobs) against the old
packer: no strategy lost a valid layout; Default, Vertical Remnant,
Horizontal Remnant and Strip complete 2-3 more strict jobs, and Strip
gains a valid one. Cost on jobs complete in both runs falls for every
strategy (Default -0.6% strict, -2.7% boxable). Default is about 7-10%
slower on the changed jobs.

Golden layouts for Default and both Remnant strategies are re-captured;
each is complete, passes NestLayoutCheck and repeats exactly.
2026-09-30 22:19:25 -04:00
aj 984d31a391 fix(packing): preserve item type and separate packing identity in clones 2026-09-30 07:24:39 -04:00
aj b688ce896f feat(engine): add built-in Rectangles and Irregular nesting engines
Moves the two production plug-in engines into OpenNest.Engine under names
that describe the jobs they suit:

- Rectangles: plain and near-rectangular plates, maximal-rectangles box
  packing (was the RectanglesNestingEngine plug-in)
- Irregular: irregular profiles, no-fit-polygon frontier packing (was the
  Opus55NestingEngine plug-in)

Their tests and the shared engine contract/layout test kit move into
OpenNest.Engine.Tests/NestingEngines.

The registry maps the old plug-in names to the new engines, so saved desktop
selections, scripts and API requests keep working, and a leftover plug-in DLL
under an old name cannot shadow its replacement. Desktop startup passes the
registry's lookup when restoring the saved Auto Nest engine.
2026-09-29 21:18:37 -04:00
aj b3a4390169 refactor(ui): route every Auto Nest engine through the validated pipeline 2026-09-29 10:56:38 -04:00
aj b8bded1b09 fix(engine): reject unrepresentable nesting output before geometry checks 2026-09-29 10:47:36 -04:00
aj 43253cd9ab feat(engine): share stock construction across nesting callers 2026-09-29 10:33:16 -04:00
aj 7aeb636ca2 fix(engine): discard cancelled pipeline results from uncooperative engines 2026-09-29 10:31:23 -04:00
aj c914d2bbd9 Add NestPipeline: one validated engine path for all front ends
build NestJob -> resolve engine by name -> Solve -> independent
NestLayoutCheck validation -> bind placements to caller drawings.
The pipeline never commits to plates and never mutates caller items;
validation failures are returned as messages naming real drawings
(unknown-requirement placements are reported, not dropped). Console,
MCP, API and desktop Auto Nest will adopt this path in later phases.

- NestPipeline.Run(request): registry-resolved engine, unknown names
  list the registered engines; cancellation propagates untouched.
- NestPipeline.Run(engine, ...): stub/plug-in engines take the same path.
- NestResultBinder: pose semantics identical to NestResultMaterializer.
- NestLayoutCheck: Violations overload with per-requirement display names.

Tests: overlap stub -> violations w/ drawing names, no throw; ghost
placement -> violation + excluded from further checks; unknown engine
-> NotSupportedException listing engines; cancelled token -> no result;
happy path -> bound by reference, caller quantity untouched.
2026-09-29 10:23:19 -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
ajandClaude Opus 5.5 1b862dc1a8 fix(engine): allow 0.0005 spacing slack in layout validation
Layouts placed exactly at the part spacing can land ~1e-4 short once
rotated, rounded (e.g. PEP's 4-decimal exports) and snapped to the
Clipper grid, so both validators rejected layouts that were correct in
practice. NestTolerances.SpacingSlack (0.0005, far below anything a
cutting machine resolves) is now subtracted from the spacing by
NestLayoutCheck's inflation and NestJobPlacementValidator's edge-distance
check. The frozen LegacyNestValidator takes the same rule so the
equivalence tests keep comparing like with like.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:19:04 -04:00
896ed2026a fix(engine): leave scribe marks out of layout bounds and salvage
The job runner already checked sheet bounds on material contours only,
but the benchmark validator and salvage scoring used Part.BoundingBox,
which includes scribe/etch moves. A PEP bend tick that ends a hair past
the part's edge passed the runner yet failed the benchmark when placed
flush to the sheet edge, and it could shrink the credited offcut. Marks
only mark the surface, so bounds and salvage now use material only.

Benchmark before/after (all five built-in engines, local fixtures,
salvage 0.5): no job changed validity or cost. Regression tests pin the
new rule: a protruding tick flush to the sheet edge is valid in all four
quadrants, and a tick past the parts envelope no longer shrinks salvage
(targeted fixture cost 130 -> 120).

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:56:49 -04:00
7f63c725e6 refactor(engine): expose the layout validation contract to engines
Engines had to reverse-engineer the benchmark validator: Opus55 assumed a
0.01 arc tolerance (the validator uses 0.001), Gpt6Astra added hand-tuned
paddings and copied the validator's check order, Qwen picked its chord
tolerance to stay under a constant it could not reference.

NestTolerances publishes the validator's arc tolerance, the Clipper grid
and SafeClearanceMargin (with its derivation). NestLayoutCheck moves the
benchmark NestValidator's checks into OpenNest.Engine as a public API
(Clears for a part pair, Violations for a whole result); NestValidator is
now a thin wrapper. Verdicts are unchanged: tests compare ordered
violation lists against a frozen copy of the old validator, and a
tangent-disc stress test covers 432 pairs at the safe margin.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:54:54 -04:00
ec5f57171f feat(engine): add RotationPolicy.EnumerateAngles and RotationCandidates
All three plugin engines turned a RotationPolicy into trial angles by hand
(fixed angle, stepped sweep, or right angles plus the minimum-bounding-
rectangle angle for Automatic), each with its own normalization, dedup and
sweep caps. EnumerateAngles gives one deterministic, Allows-checked list;
RotationCandidates.ForShape adds the MBR-aligning angles via the existing
Polygon.FindBestRotation, and DistinctOutlines drops angles where the part
looks identical. A cap of one returns the sweep start rather than throwing,
since engines request a single sample for small orientation budgets.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:33:52 -04:00
dd1a958f5c feat(engine): expose NestJobCost, the benchmark's scoring
Engines optimized guesses at the benchmark cost: Opus55 re-implemented
salvage credit, Qwen used plate area per part area, Gpt6Astra ignored
salvage. NestJobCost moves StockLadder's EstimateNetArea into a public
home (net sheet area, unplaced-part penalty, whole-result Evaluate) and
the benchmark and StockLadder now call it. Scores are unchanged: tests pin
it against a frozen copy of the old computation and real benchmark runs.
Bounds still include marks, as before, so scores do not move.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:12:07 -04:00
ae2b0beb45 feat(engine): expose JobPartGeometry for reading snapshot material
Every plugin engine rebuilt part material from a snapshot by hand and
filtered only rapids, so all three kept counting scribe/etch marks as
material after 1b5e1b1 fixed it in the host. JobPartGeometry is the
validator's own reader made public: SpecialLayers.IsMaterial, validated
closed contours, material area, and TryRead returning null for unreadable
parts. The job validators now use it, so engines and validation agree.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:12:07 -04:00
ajandClaude Opus 5.5 6ab45e6de7 test(engine): seed spacing brute-force sampling deterministically
The seed came from string.GetHashCode, which .NET randomizes per process,
so each run drew different samples and the "ring" case occasionally drew
fewer than six rejections and failed its coverage assertion (1 in 6 runs),
even though every validator decision matched the brute-force reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:12:07 -04:00
029299ccf8 feat(engine): add NestJobResultBuilder for engine result assembly
Engines assembled NestJobResult by hand - instance indices, fulfillment,
stock usage, status and PlateCommitted progress - and Qwen38FlashNext got
PlateIndex wrong (stock index instead of sheet order). The builder assigns
plate and instance indices itself and rejects overproduction and exhausted
stock, so engines only decide placements.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:55:10 -04:00
4cdb39870b feat(engine): expose NestPlateStock.WorkArea, Area and Fits
Every plugin engine re-derived the quadrant/edge-spacing work area by hand
(Gpt6Astra, Opus55 and Qwen each had a copy, as did the placement
validator). One definition on the stock removes that duplication and the
chance of an engine disagreeing with the validator's bounds.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:55:10 -04:00
ajandCodex 95833236cf docs(engine): clarify priority so scarce stock favors lower numbers
Document constructor and property semantics and cover priority zero versus nine on one sheet.

Co-Authored-By: Codex <noreply@openai.com>
2026-09-25 07:53:16 -04:00
ajandClaude Opus 5.5 1b5e1b14a6 fix: leave etch/scribe marks out of nesting geometry
Every nesting-geometry consumer filtered only rapids, so scribe/etch
moves counted as part material. An etch tick that ends a hair outside
the outline (PEP bend ticks start on the notch edge) made the part
"open geometry leaving the material region": the job validator threw
and every built-in engine plus Gpt6Astra crashed on real PEP jobs
(PT75, drawing 4980 A01 PT77). Marks are only on the surface, so they
should never affect placement, collision, area, or validation.

- SpecialLayers.IsMaterial excludes Rapid and Scribe; used by drawing
  area, canonical angle, part collision, PartGeometry, plate perimeter,
  best-fit/pair evaluation, rotation analysis, GPU evaluators, and both
  validators. Timing, display, splitting and posts still see marks.
- ConvertGeometry also maps the saved SCRIBE layer name to Scribe, so
  programs rebuilt from stored entities keep their marks.
- NestReader repairs older files (e.g. PepNestExport output) whose
  programs saved etch as cut moves while source entities kept SCRIBE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 06:44:29 -04:00
ajandClaude Opus 5.5 dc9e83ef17 perf(jobs): flatten validator contours once, at a 0.001 chord tolerance
The candidate validator flattened every arc into 1000 segments and rebuilt
both parts' polygons and edge lists for every pair it compared, so spacing
checks on filleted parts cost millions of edge pairs each. Validation, not
the fill pipeline, was nearly all of a solve's wall time.

Placed contours are now flattened once with ToPolygonWithTolerance(0.001),
the tolerance the benchmark NestValidator and Part.Intersects already use,
and each part's shape is built once per candidate. Arcs stay inscribed, so
a layout placed exactly at the spacing still passes.

12-nest PEP corpus, Default + StockLadder, --parallel 1: 2820 s -> 227 s.
Every run that finished before gives the same validity, count, plates and
cost. Three StockLadder runs that used to hit the 5-minute timeout now
finish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 14:29:43 -04:00
aj 8e4735d25d [verified] add benchmark baseline and rotation fixes 2026-09-22 16:35:25 -04:00
aj e65f849c7a refactor(engine): remove legacy nesting engine surface 2026-09-22 14:25:46 -04:00
aj 88966d118c refactor(engine): route console and MCP nesting through named job engines
Console --engine now names a jobs engine for --autonest (solved once
through NestingEngineRegistry.Create and committed onto the plate) or a
built-in fill strategy for single-plate fill through the public
PlateFillService; unknown names exit with the valid choices instead of
consulting the process-global legacy registry. MCP nesting tools take an
explicit engine argument per call with the session default, never read
process-global active-engine state, and reject whole-job engine names on
single-plate fill tools. NestingEngineRegistry gains an explicit Create
(name) resolution; PlateFillService gains a public ResolveStrategy and a
plate-number Nest overload used by interactive callers.
2026-09-22 00:30:03 -04:00
aj 856dbfd8af refactor(engine): add public plate fill service 2026-09-21 19:51:35 -04:00
aj eb8fbec1aa refactor(engine): make jobs plate nesters filler-backed 2026-09-21 18:08:25 -04:00
aj 073ead9b79 refactor(engine): share jobs placement identity and progress mechanics 2026-09-21 16:36:24 -04:00
aj ed908b9706 refactor(engine): extract strip plate filler 2026-09-21 15:40:13 -04:00
aj e69ec07830 refactor(engine): extract default and remnant plate fillers 2026-09-21 15:27:03 -04:00
aj eafa0fab01 refactor(engine): extract plate fill orchestration 2026-09-21 15:01:00 -04:00
aj 9b9e510510 refactor(engine): extract progress reporting seam 2026-09-21 14:43:46 -04:00
aj 451841401a refactor(engine): align namespaces with directory layout under OpenNest.Engine
All 132 OpenNest.Engine source files now declare namespaces matching
their nested directories: Jobs/, Jobs/Placement/, Jobs/Adapters/,
Fill/, RectanglePacking/, CirclePacking/, and engine-root types moved
from 'OpenNest' to 'OpenNest.Engine'. RootNamespace updated accordingly.
Consumers (Api, Console, Mcp, Benchmark, Training, desktop app, tests)
gained the explicit usings the move requires; CLAUDE.md updated.
2026-09-21 13:18:34 -04:00
aj fd3375cde6 test(engine): golden-layout parity fixtures for all four strategies
Pin committed fulfillment and exact placement poses for Default, Strip,
Vertical Remnant, and Horizontal Remnant through the production
PlateNesterFactory + NestJobRunner path, plus deterministic poses for
OrderedPlateNester via its StockLadder wiring (no legacy counterpart) and
authoritative progress-stage sequences for StockLadder and
FixedStrategyNestingEngine. Captured at 42bbde7 (post ShrinkFiller axis
fix); these fixtures are the regression net for the legacy-engine removal.

Note: Strip is pinned on a rectangle-variety job. On dense mixed-shape
jobs the iterative shrink path intermittently proposes overlapping
candidates (pre-existing scheduling nondeterminism), so its mixed-geometry
layout is deliberately not pinned.
2026-09-21 12:17:24 -04:00