Slice 3 of common-line cutting. On costly plate (N26-295: 3/16 RA310S at about
$2,500 a sheet) pairing parts one kerf apart fits the part the spacing cannot:
the last plate's 38 parts fit on one 48x120 with Auto Nest only with pairs.
- Drawing Properties gains "Allow common line" (Drawing.AllowCommonLine, off by
default because customer edge-quality needs vary). The nest's CommonLineUsage
(Off / When it adds parts / Whenever available) is set in Auto Nest and seeded
from Tools > Nest Defaults; both persist (nest.json, defaults.json).
- NestPipeline runs the ordinary solve, then, with a level on, trials where
eligible parts are offered as kerf-gap pair compounds. Placed pairs expand into
parts plus explicit NestJobCommonLine groups, are validated again, and
CommonLineRanking keeps a trial only by the level: count first, then sheets;
"when it adds parts" keeps the plain layout on a tie, "whenever" also takes a
shorter cut or less material, never at the cost of a part. Engines, FillScore
and their comparers are unchanged; Off asks the engine exactly what it did.
- NestLayoutCheck exempts members only through a sheet's explicit group list and
only when the job allows common lines; materializing and committing create
Plate.CommonLines.
- Server nesting writes common-line jobs as version 3 and replays the recorded
trial answers, so the desktop binds the same groups.
- Fill Area honors the level through a PlateFillService overload.
- The benchmark gains --common-lines <level> --kerf <k>.
Benchmark (synthetic nests + N26-295, --parallel 1): with no flag and with
--common-lines off every engine matches the base commit; with a level on no
layout becomes invalid and three runs save a sheet at equal nested area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Fill" said nothing about what the engine does, since every engine fills.
It is now Lattice, and Fill stays an alias, so saved selections, scripts
and API requests keep working. Engine descriptions are rewritten in plain
language and shown beside each name in the desktop engine lists; the main
toolbar's tooltip also says when Fill Area falls back to Lattice.
Docs also cover the leftover placement from the previous commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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).
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>
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.
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and
line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests
pass after reformat, full solution builds 0 errors.
Added .csharpierignore so csproj/config XML keeps its existing layout
(CSharpier's XML wrapping churns attributes with zero benefit).
Formatting is now enforceable: dotnet csharpier check . passes.