Files
OpenNest/docs/automatic-nesting.md
T
aj 4226c0f902 fix(engine): find the fewest clamps to remove, not only neighbouring runs
Recovery only tried contiguous runs of clamps, so a part with a notch over
a middle clamp had all three clamps removed although the two outer ones
were enough (D8: 'the fewest clamps that let it fit'). One probe per clamp
now finds the clamps every removal must include (a part that cannot fit with
only that clamp left needs it off; removal is monotone), and the search
tries every set by size, mandatory clamps plus the optional ones nearest the
origin first. Full-sheet parts resolve with one probe per clamp.

PM 3df03af2-35a6-49c2-9082-fc6208f5b708
2026-10-13 18:15:50 -04:00

21 KiB

Automatic nesting pipeline

NestPipeline.Run is the shared whole-job boundary in OpenNest.Engine:

  1. Snapshot caller requirements and stock into a NestJob.
  2. Resolve the selected name through NestingEngineRegistry (built-ins and plug-ins use the same path).
  3. Run the engine without mutating caller drawings or plates.
  4. Check returned placements with the independent NestLayoutCheck used by the benchmark.
  5. Consolidate exact ordered nonempty layouts only when the entire result is valid, then bind each layout to the caller's drawing references; the caller decides whether to commit it.

NestStockBuilder.FromTemplate snapshots sheet options and spacing settings for multiple-sheet jobs. SinglePlate offers exactly one physical sheet, regardless of the legacy plate repeat quantity. NestPipelineCommit.ApplyToEmptyPlates applies accepted desktop proposals to empty/new plates with the checked size, quadrant, spacing and total-copy quantity semantics. Quantity is set before parts are attached, so drawing counts multiply exactly once. It never fills occupied plates.

Repeated layouts and physical sheets

Raw NestJobResult remains expanded: one entry per physical sheet, with physical stock usage and original instance indices. NestPipelineResult.Raw is not rewritten. Valid exact duplicates in the newly generated proposal become one ProposedPlate with positive Quantity (total copies, including the first; default one). Its three-field constructor/deconstruction remains supported. PlateIndex retains the first raw occurrence, and proposed layouts retain first-occurrence order.

Identity includes stock ID and exact stock settings plus the ordered requirement IDs and IEEE bits of X, Y and rotation. Instance indices do not affect grouping. Different same-name drawings, reordered parts, near-equal poses, signed zero differences, or different stock entries stay separate. No rounding, geometric equivalence or merging with existing occupied plates/cutting programs is performed. Empty sheets and all invalid-but-representable proposals stay ungrouped, preserving physical review indices; unknown requirements and nonfinite poses remain unkeepable. Drawing and bound-program freshness checks still apply to every grouped layout and its correspondence to the raw physical sheets.

Layout-entry count is not physical-sheet count: sum layout quantities for sheets, and multiply each layout's part counts, area, cutting time and costs by its quantity. .nest already stores Plate.Quantity; no file-format change is required. Exact output grouping does not itself remove engine searches.

Desktop Auto Nest

Every engine selected in Nest > Auto Nest uses the pipeline. Part-First and its sorting controls are removed. Use interactive Fill Area/remnant tools for leftover space on occupied sheets; these are not whole-job nesting.

Auto Nest starts on empty/new sheets and does not change existing populated plates. Stock Options offers sheet sizes and salvage settings; minimum salvage size is part of that section, not a Part-First option. When enabled, its grid keeps a blank last row for adding another size (W x L) and cost, including after loading saved options. Unused blank rows are not offered as stock. The maximum remains 100 physical sheets per run.

Progress is owned and modal so the input drawings cannot be edited while a worker uses them. Stop or closing progress cancels and discards the whole proposal. The dialog waits for the worker to finish; no engine has an Accept-early button on this path. A plug-in that ignores the cancellation token cannot commit its late result.

Every invalid result shows the validation report before any plates are changed:

  • Discard is the default button and the Escape action. No proposed parts or sheets are applied.
  • Keep anyway requires an explicit click and keeps the entire representable proposal, then enables Overlap Check.
  • Malformed output (unknown requirements or nonfinite poses) is not keepable. It cannot be faithfully represented as the proposed layout; no partial/truncated proposal is offered.

Overlap Check displays material overlaps, not every spacing/stock/rotation failure in the report. A layout passing validation may still be incomplete; completeness and stop reason are separate from geometric validity. This check does not replace pre-post CNC verification.

The selected engine is saved in %APPDATA%\OpenNest\engine-selection.json. Selection is restored after plug-ins load. A saved name from an earlier release maps to the engine that replaced it (see nesting engines). If the saved engine is unavailable, Default is selected and the status bar reports the fallback; startup does not replace the saved missing-engine preference.

Machine edge clamps

A nest can carry the machine's fixed clamp rail: identical rectangular clamps (width along the edge, depth into the sheet) at X centers measured from the plate origin along the origin-side edge. Nest > Clamps... edits the current nest's rail. OK applies it to every plate of the nest (Cancel discards the edits); removing every center removes the clamps. Load Machine Defaults only fills the dialog. Save as Machine Defaults writes %APPDATA%\OpenNest\clamps.json immediately, with the clamps, their units and whether new nests receive them; Cancel does not undo that write. The dialog refuses to open while nesting or a plate action is running.

Clamps are off by default. Until clamps.json is saved with clamps enabled and at least one center, New and BOM import create nests without clamps exactly as before. When enabled, a new nest gets the rail converted to its units. A missing file keeps clamps off; an unreadable file, or one with ignored invalid values (a bad clamp center is dropped), warns once per session that new nests may not match the machine.

Every plate follows the nest's rail at its own size and quadrant: clamps wholly beyond a shorter sheet are left off it, one still reaching onto the sheet keeps its whole footprint as the avoidance area (it may hang past the sheet edge), and resizing or rotating a plate re-resolves its clamps. Auto Nest offers each stock size with the rail resolved for that size, and every committed sheet keeps exactly the clamps its layout was validated against. Clamps are drawn as hatched amber rectangles over the sheet; they cannot be selected, moved or sequenced and never count as parts. Interactive fill, pack and push keep parts clear of clamps, Check Overlaps reports parts on a clamp and manual cut-offs crossing one, automatic cutoffs never place a cut through a clamp, and pre-post verification warns (with per-attempt consent) about parts, cut-offs and direct rapids crossing an active clamp - including the first rapid when a clamp covers the program start point.

Last-resort clamp removal

When a part cannot be placed on an empty sheet of any offered size with every clamp in place - neither the selected engine nor the Irregular or Rectangles lane finds a clamped placement - Auto Nest takes the fewest clamps off one sheet that let it fit (not necessarily neighbours: a notch may let a clamp between them stay; the clamps nearest the origin on ties) and nests it on extra sheets with those clamps removed; other unplaced parts may share the freed sheets. It never removes a clamp to save material, never re-plans a sheet the normal nest already used, draws only on stock that is left, and only removes clamps resolved from the machine rail. The completion summary lists "Sheet N: remove clamp K (+M min)", the sheet shows each removed clamp dashed with "remove", and each removed clamp on the sheet adds the rail's removal minutes (default 5, set in Nest > Clamps...) to that sheet's cut time.

Limits: the search stops after 1000 single-part trial solves and then keeps the normal result, removing nothing; and a part whose material clears a clamp by less than the engines' 0.0001" grid may still be reported as needing a removal.

Integration constraints

Callers must hold drawings and target state stable from snapshot through attachment. Do not append whole-job placements to an occupied target or flatten multiple returned sheets onto one plate: that would commit a layout different from the one checked. Do not trust arbitrary engine fulfillment metadata as a substitute for counting returned placements.

Interactive fills are outside this contract and still use PlateFillService. The benchmark invokes the same independent validator.

The pre-pipeline multi-plate orchestrator and plate-size optimizer have been removed from OpenNest.Engine; this is a source and binary break for external code that called them. Whole-job callers use NestPipeline.Run (or implement INestingEngine as a plug-in); multiple stock sizes and salvage credit are expressed as job stock and NestJobOptions.

Console and MCP

Console --autonest uses the jobs engine named by --engine (Default when omitted) against one physical sheet. The selected plate's old parts are replaced only after acceptance; other plates are unchanged. Default demand is still one of each drawing unless --quantity is supplied. A partially fulfilled, valid result may be saved; a successful placement is not a claim that all demand was met.

Invalid output is printed and rejected with exit code 2 without saving or posting. --allow-invalid explicitly accepts representable layout violations. Malformed output, multiple returned sheets, and zero placements are never saved by this path. Unknown engines exit 1. --autonest --keep-parts rejects an occupied target: use the plain interactive fill path for existing obstacles instead.

MCP autonest_plate requires an empty target. The stdio server serializes all tool calls sharing its mutable session, so another request cannot change drawings or occupy a target during a solve. allow_invalid defaults to false. It reports violations and makes no changes on rejection, including with an override when the output is unrepresentable or contains multiple sheets. engine defaults to Default, and its description lists the built-in engines with what each suits. Fill tools (fill_plate, fill_area, fill_remnants, pack_plate) remain separate and default to the Fill strategy. Console and MCP load jobs plug-ins from Engines/ beside their executable.

MCP pack_plate and autonest_plate report each requested drawing's newly placed count and remaining quantity, indexed by request; partial fulfillment is explicitly warned about. Per-sheet ratios are not enforced or required: an uneven sheet is acceptable if the remaining quantities are placed elsewhere in the nest. These two plate tools operate on a single selected plate and do not automatically finish a multi-sheet job; callers can use autonest_job for that. Duplicate request tokens and ambiguous drawing names are refused before placement. A zero-quantity pack request places zero parts for that drawing; negative quantities are rejected.

autonest_job(plateIndex, drawingNames, quantities, engine?, no_new_plates=false, sheets?) is the whole-session completion path. Quantities are total targets across existing placed parts (physical plate quantity included) plus newly proposed sheets. Without sheets, the selected empty cutoff-free plate supplies stock size, spacing, quadrant, grain and cutting settings; other empty plates with equivalent persisted settings may be reused first, then additional matching plates are created as needed. no_new_plates=true caps physical stock by the number of existing matching empty plates, not the number of grouped output layouts or their old repeat quantities. Exact repeats still attach once with their total quantity; redundant empty entries remain unchanged and are omitted on save. Empty entries consumed by those physical copies are reserved by their populated layout in the live MCP session and cannot be offered again or filled by another nesting tool while that owner remains populated. Clearing or removing the owner (including replacement by load_nest) releases only its surviving placeholders; clearing or deleting a placeholder does not release the owner's other copies or another layout's reservations. Genuinely unused entries remain available. No automatic deletion of user plates occurs. No new layout entries are created in this mode. The request uses the independently validated NestPipeline and commits only when every requested quantity is met; invalid, incomplete, cancelled or unknown-engine proposals leave existing plates and counts unchanged. A no-placement solver result is not proof that geometry cannot fit. pack_plate and autonest_plate remain single-sheet operations.

For finite mixed-size inventory, supply sheets as an array, for example {"plateIndex":0,"drawingNames":"large,small","quantities":"1,1","engine":"Rectangles","sheets":[{"width":60,"length":120,"quantity":1},{"width":48,"length":96,"quantity":2}]}. Width and length have the same units and orientation as create_plate. Every row's positive integer quantity is the total physical sheets available for this solve, including any compatible pre-created empty plates; it is never a Plate.Quantity repeat multiplier or additional stock. The selected plate supplies common spacing, edge spacing, quadrant, grain and cutting settings, but is reused only if its size matches a chosen sheet. Other compatible empty sheets can be reused; occupied or cutoff-bearing sheets cannot. Only needed different-sized sheets are created. Duplicate size pairs, nonpositive or nonfinite dimensions, unusable work areas, nonpositive quantities, an empty list and excessive total inventory are refused before solving. sheets cannot be combined with no_new_plates=true. Omit sheets to retain the previous single-size behavior. Successful output reports stock-0, stock-1, etc. in input order with physical used/remaining counts, reused/created layout counts, plus committed session layout index to stock ID and per-drawing totals. Stock is request-local, not a persistent purchasing catalog. Unused empty templates may be omitted by the .nest writer on save.

MCP delete_plate(plateIndex) removes only a sheet with no parts and no cutoff definitions, from either loaded or newly created session plates. Occupied, cutoff-bearing and invalid indices are refused without changing the session. After deletion every later index shifts down by one; inspect get_plate_info before further edits. This is session cleanup, not a data purge or a save-time option. save_nest lists all session plates in its response, but the existing .nest writer omits empty cutoff-free plates from the serialized file; reloading may therefore have fewer plates than the response says. There is no opt-in switch to preserve empty plates in the file.

MCP engine development harness

test_engine builds and runs OpenNest.Console in a configured, trusted checkout. nestFile is required; there is no machine-specific sample default. Drawing, plate and output arguments and the stdout / === Errors === / nonzero-exit response format are unchanged. Relative nest/output paths resolve from the MCP server's working directory, before launching the checkout's console.

The existing .NET host configuration accepts:

  • EngineHarness:SourceRoot: required absolute checkout path containing OpenNest.Console/OpenNest.Console.csproj.
  • EngineHarness:DotnetPath: optional existing absolute executable path; defaults to the dotnet host in the active .NET installation, never a PATH search.
  • EngineHarness:TimeoutSeconds: positive integer, default 120, maximum 2147483. The single deadline covers build/run, process exit and both concurrent output drains. MCP request cancellation uses the same bounded path.

For environment variables, use EngineHarness__SourceRoot, EngineHarness__DotnetPath and EngineHarness__TimeoutSeconds. These are server/operator settings, not caller-supplied executable or checkout overrides.

Timeout/cancellation returns a clear error, kills the owned live process tree, waits up to five additional seconds for direct-process cleanup, and closes local pipe readers. Cleanup failures are reported. A descendant already detached when its parent exits cannot reliably be found by Process.Kill(entireProcessTree); it may remain alive, but inherited pipe writers cannot hold the tool open. The harness does not scan for or kill unrelated processes. Windows process-tree semantics still need Windows runtime acceptance; the private process regressions also execute on Linux without customer nest files.

.NET API and saved responses

Set NestRequest.Engine to a registered engine name; null retains PlacementStrategy / legacy Strategy behavior. Library hosts own plug-in discovery via NestingEngineRegistry.LoadPlugins before calling the API. Explicit request requirement IDs are preserved in response fulfillment even when multiple requirements use the same source DXF.

The API returns a detached proposal. ValidationStatus is Valid, Invalid, or Unrepresentable, and Violations lists the problems. Representable invalid proposals retain their parts for caller review; unrepresentable proposals contain no sheets. Consumers must inspect validation before applying, quoting or posting the proposal. Status describes fulfillment, not acceptance: counts, stock usage and completeness are derived from returned placements rather than trusting plug-in summary metadata.

Response archive schema 4 records grouped-layout semantics. SheetCount is the physical sum of Nest.Plates[*].Quantity; fulfillment, stock usage, utilization (part area / physical sheet area), cutting time and costs are quantity-weighted. PlateStockMappings[*].PlateIndex indexes the actual materialized Nest.Plates layout entries, so mappings may be fewer than SheetCount. Schema 3 and unversioned archives still load unchanged: no regrouping or metadata reinterpretation occurs. Validation status and violations remain persisted; older archives without validation metadata load with null status, which is not a successful check. Saving a proposal archive does not authorize CNC posting.

Verification

dotnet test OpenNest.FrontEnd.Tests/OpenNest.FrontEnd.Tests.csproj exercises Console, MCP and API rejection/override, cancellation, physical-sheet settings, response IDs and archive round-trips. Its current target matches MCP's net8.0-windows marker but does not use WindowsDesktop and executes on Linux as well as Windows. It is included in the solution and Windows test workflow. Desktop interaction tests remain in Windows-only OpenNest.WinForms.Tests.

Stock costs

Auto Nest Stock Options accepts a generic per physical sheet Cost. Enter positive finite costs on every active row in one common unit (purchase price, area, or another consistent scalar). All-zero rows retain legacy area scoring: zero means unspecified, not free stock. Negative/nonfinite costs and mixed zero/positive rows are rejected before solving. The blank add row is ignored.

MCP autonest_job accepts optional cost on each sheets row, alongside width, length and positive finite quantity. API NestRequestPlate.Cost uses the same nullable contract: omit every cost for area scoring, or supply strictly positive finite values for every available row. Explicit zero is invalid in these requests. Engine stock with quantity zero is unavailable and does not select the cost mode. MCP continues to reject duplicate sizes; API/engine stock IDs keep independent inventory.

With prices, gross sheet cost is the supplied value. Salvage is still computed from the largest qualifying material-edge offcut; net cost is Cost * (NetSheetArea / SheetArea). Set desktop salvage rate to zero for purchase-price comparisons. Entering prices preserves the selected salvage settings. MCP and API currently disable salvage. Without prices, scores retain their existing area units and tie ordering; fixed strategies retain their legacy no-salvage area tie-break.

Desktop reports gross cost, estimated salvage credit and net score. MCP reports basis, each stock's used physical count, supplied cost and gross subtotal, plus gross total, estimated salvage credit and net score. API exposes these under Costs (Basis, Stock, GrossTotal, SalvageCredit, NetScore) and persists them in quote archives. For example, two sheets costing 25 each report gross total 50; with salvage disabled, credit is 0 and net score is 50. area identifies fallback scoring; supplied-cost identifies explicit prices. Totals cover the returned proposal, not previously occupied session sheets, and exclude penalties for unplaced demand. Invalid API proposals have no cost summary. Overflow fails instead of becoming a score.

An accepted MCP solve stores requested drawing quantities and offered stock prices; save/reopen preserves them and existing nest metadata. Rejected/cancelled proposals change neither requirements nor options. Inventory remains request-local; saved options are offers, not a per-sheet purchase ledger. These are cost-aware heuristics, not global cost optimizers; validation and fulfillment remain required.