Commit Graph
51 Commits
Author SHA1 Message Date
ajandClaude Opus 5.5 b026bd19d2 feat(common-line): place parts on common lines automatically at a chosen level
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>
2026-11-05 19:38:00 -05:00
ajandClaude Opus 5.5 f9b2713420 feat(common-line): cut each shared line once through Plan Cutting and the CI Fiber post
N26-295's PT42 pairs saved spacing in slice 1 but every shared edge was
still cut twice, costing a pass per edge and risking a freed part
tipping under the head on the second pass.

Plan Cutting now cuts a plate's common lines once unless the plate is set
to cut them twice. Each group is cut member by member, owner before
partner (a captured dependency that search and replay enforce, so a kept
order cutting the partner first is a constraint conflict): the member's
holes, then the shared cuts it owns as open CommonLine centerline cuts
entered through the notch on the span's axis, then its remainder pieces,
each ramped in from the kerf the shared cut opened. Capture marks the
shared spans as tabs on the owned clean copy, so replay's contour
accounting proves each perimeter is cut exactly once; a lead that fits
at neither end refuses as UnsupportedGeometry naming the span, never
shortened. Material still comes from the drawing, so utilization,
quantity and overlap results do not change. Members are never
support-tabbed; locked or excluded members and broken groups refuse.

The CI Fiber post writes shared cuts in the cut-off form with a lead-in
and no G41/G42, and remainders in the contour form on their perimeter's
compensation side, named by a remainder comment. Cincinnati CL (whose
PreApplied mode offsets nothing) and Gravograph refuse such nests, as do
the desktop and console for any post without ICommonLinePostProcessor;
nothing falls back to a silent double cut. Plate > Arrange > Cut Common
Lines Twice keeps slice 1's behavior, persists with the plate and makes
an open plan stale.

Pre-post review now checks each span is cut once, on its centerline, by
the member cut first, and the rapid check treats a finished member's
remainders as an obstacle. Compensation start in void and cancel in
kerf are unconfirmed on the CI FIBER 4020; release waits on a 3/16
stainless coupon trial.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:51:42 -05:00
ajandClaude Opus 5.5 1d87d212de feat(common-line): place parts one kerf apart to share an edge
Expensive alloy jobs (N26-295, 310 stainless) could not fit their last
PT42 at full part spacing; the plate was squeezed to 0.2 spacing with a
part 0.14 from the edge. Parts whose straight edges face each other one
kerf apart are cut on the same centerline, saving the spacing along each
shared edge.

Plate.CommonLines holds groups of parts and the kerf they were joined
at. Shared spans are derived from the placed clean perimeters, never
stored, so a moved member breaks its group instead of leaving a stale
span. Plate > Arrange > Join Common Line slides the selected parts
together at the plate's kerf; Remove Common Line ungroups them. The
plate draws shared spans as a dashed centerline.

Both spacing checks hold group members to the kerf instead of the part
spacing, and a new pre-post Common lines check reports broken groups, a
changed kerf, and leads that run into a partner. Groups persist as part
positions in an optional commonLines array that older readers ignore.

This first slice still cuts every shared edge twice; single-pass cutting
through Plan Cutting and the posts comes next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 14:16:13 -05:00
ajandClaude Opus 5.5 fbc23d6b64 feat(nesting): nest higher priority numbers first
Priority worked like a rank (lower first) with a default of 0, and the
Edit Drawing form stopped at 0, so moving one part ahead of the rest
meant renumbering every other drawing. Higher-first lets a user raise a
single part to 1 and leave everything else at the default.

Every engine, filler and candidate comparer now serves the highest tier
first. Stored values keep their order:
- .nest files add `nestPriority` and keep writing the negated value under
  `priority`, so older readers nest in the same order; files without
  `nestPriority` are negated on read. Drawing packages strip both, so
  package hashes are unchanged.
- Library settings send both keys the same way for older clients.
- Server schema v18 negates stored drawing priorities (an older server
  refuses the upgraded database).
- Job documents move to version 2; version 1 jobs are converted, and an
  older worker refuses version 2 instead of reversing the order.

The Edit Drawing form now accepts negative priorities and says higher
numbers nest first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:02:07 -05:00
ajandClaude Opus 5.5 d29f919aa2 fix(io): save nests safely and reopen them with their cut-off settings
Reopening a nest regenerated its cut-offs with built-in defaults (no
overtravel, a different minimum segment) rather than the machine's
settings it was made with, so a nest saved, reopened and posted could
cut scrap that never separates. Plates now record the settings their
cut-offs were last generated with, and files carry them; older files
still load with the defaults.

Saving over a nest file truncated it before writing, so any failure
part way lost the only good copy, and the error reached the user as an
unhandled exception after the save path had already changed. Nest and
settings files are now written beside the target and swapped in, and a
failed save is reported with the old file left intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:51:11 -05:00
ajandClaude Opus 5.5 ca693285bb feat(cad): add radius and diameter dimensions
Holes and radii need sizes on the drawing as well as the overall extents. Select
circles or arcs, right-click and choose Add Radius Dimension or Add Diameter
Dimension. The value is measured live from the curve, found by its entity ID, so
it follows edits; only the leader angle and label offset are saved. A radius on
an arc stays on its sweep, and labels are kept clear of the curve and center.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:47:56 -04:00
ajandClaude Opus 5.5 abced0a7ec feat(cad): add overall dimensions to the CAD Converter
Shops need a part's overall size on the drawing while converting it. Width and
height are measured live from the geometry that will be cut, so they follow
filters, exclusions, simplifying, breakoffs and tabbed cutouts instead of going
stale. Only the placement is saved; dimensions never touch the program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:34:31 -04:00
ajandClaude Opus 5.5 4028dafb26 feat(cutoffs): trim a middle piece into a gap in the line
Trim refused the piece between two crossing cut-offs because a cut-off had
one start and one end, so a line could not be stopped short around a pocket
of parts without deleting it and drawing two. A cut-off now carries Gaps,
stretches between its limits left uncut: segment generation excludes them
like parts, so the line splits into segments that each take their own place
in the cut sequence. Trimming a middle piece adds a gap from one crossing
centre to the other (merged with any it touches), Extend near a gap's edge
closes it, and Swap Through-Cut refuses a junction a gap touches rather
than guess which side of the gap should run through.

Every reader of the limits treats a gap as uncut, so nothing waits for,
warns about or credits a cut that never happens: segment spans stop at a
gap, the cutting dependencies skip a part inside one, the clamp check
ignores a clamp inside one, the cutting-state and overlap stamps go stale
when gaps change, and a gapped line is never full span for the expected
remnant or automatic cut-offs. Gaps persist as an optional gaps array;
older versions ignore it and would cut straight through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:46 -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 Fable 5.1 fa7a1f0fce feat(io): persist a plate's sheet outline
An optional per-plate `outline` key holds the vertices and obstacles;
rectangular plates write nothing new, an empty outlined plate is kept,
and a malformed value loads the saved size as a rectangle with a reader
warning. Undo snapshots restore it through the same path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-28 22:16:36 -04:00
ajandClaude Opus 5.5 11b04cc272 fix(io): save each part's cutting parameters with its lead-in program
PartDto had no field for Part.CuttingParameters, and restoring a saved
program cleared it, so after save and reopen the lead and pierce-zone
reviews judged manual and planned leads against the plate's settings
instead of the ones they were built with. Undo/redo will restore nests
through the same reader, so it would have carried the same loss.

The settings are written only beside a saved part program and read
back after the program installs. Older files and clean parts read null,
as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:11:39 -04:00
ajandClaude Opus 5.5 23f093fcc1 feat(nest): plates report the remnant they are expected to leave
Core PlateRemnant.Expected picks a full-span cut-off drop clear of the
parts (less kerf, nominal position for head-locked overcuts), else the
largest salvage edge offcut, else none below the nest's new persisted
MinimumRemnantDimension. NestJobCost's salvage offcut now calls the Core
rule unchanged. NestWriter writes a derived plate expectedRemnant that
readers skip, and /sheets reports it per plate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 17:40:44 -04:00
ajandClaude Opus 5.5 15750949dd feat(nest): plates name the ledger item each sheet is cut from
Auto Nest on MaterialLedger stock now records which inventory item each
physical sheet uses, so ML can hold exactly those items instead of matching
by size.

- Core: Plate.StockItemCodes (entry i = sheet i of Quantity; empty = uncoded).
  Core invalidates it: a resize clears it (turning the sheet keeps it), a lower
  Quantity truncates it, a nest material/grade/thickness change clears every
  plate's, ClearAll clears it and new plates never copy it.
- Desktop Auto Nest: after a kept proposal is committed, LedgerStockAssignment
  deals each ledger row's codes to its plates in Nest.Plates order (bundles
  repeat their code; clamp-removal variants use their base row). Rejected,
  discarded or cancelled proposals never reach it. NestPipelineCommit.PairWithStock
  pairs committed plates with their solved stock; the engine stays ledger-free.
- Plate list tooltip: "Stock: CODE (location)"; locations are display-only.
- nest.json: optional plate stockItemCodes, omitted when empty, read leniently.
- /api/nests/{id}/sheets: new plates[] { index, width, length, quantity,
  stockItemCodes }; sheets[] unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 17:40:44 -04:00
ajandClaude Opus 5.5 48863d51c8 feat(support): add slat support beds alongside the grid
Slats are strips of a set thickness running the full length or width of
the table, one every pitch. A part's contacts are the corners of where its
material crosses a slat, so the support check and support tabs work as on
the grid. Nest > Support Bed... picks grid or slats; nest files store
type "slats" and support-grid.json keeps both layouts' values. Draw
Support shows the slats cut off to the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 15:33:12 -04:00
ajandClaude Opus 5.5 9c78b4ebec feat(machines): assign each nest a machine and keep the support grid per machine
The support grid was one shared support-grid.json, off by default, and a nest
had no machine at all, so a plasma table and a fiber laser could not have
different grids, and nothing could pick machine-specific behavior later
(lead-ins differ between laser and plasma; tabbing depends on the grid).

- Nest.Machine (id + name) persists as top-level `machine` in nest.json;
  malformed values load as none.
- MachineConfig.SupportGrid holds each machine's grid; a machine without one
  still falls back to the legacy support-grid.json.
- New and BOM import assign the Tools > Nest Defaults machine (first by name
  when none is chosen) and give the nest that machine's grid.
- Nest > Machine reassigns a nest and swaps in the new machine's grid;
  Nest > Support Grid saves machine defaults to the nest's machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:17:28 -04:00
ajandClaude Opus 5.5 993555dba6 feat(cutoffs): head-locked overcut at the far edge, reversible by Plan Cutting
Stock can arrive up to 1.5 in wider than nominal, so only the origin-side
edge is certain. A cut-off with a head-locked overcut cuts past the nominal
far edge with the head height locked, covering the oversize. With that lock,
Plan Cutting may start the far-edge segment at the far edge: overcut outward,
rapid back still locked, unlock, cut toward the origin. Without it the
direction stays fixed, as decided in Project Memory a20ea455.

- CutOff.HeadLockOvercut / FarEdgeFirst, persisted in .nest; HEAD HEIGHT
  LOCK/UNLOCK comment markers; far edge is quadrant-aware.
- Planner offers the reversed far-edge segment in the joint search, the
  safest route and segment insertion; Apply records the direction.
- Cincinnati CL and CI Fiber posts gain lock/unlock code settings and refuse
  head-locked cut-offs while either is empty.
- Automatic cut-off dialog setting (off by default, 1.5 in), kept between
  sessions and used for new cut-offs.

Lock codes and machine behaviour with the head locked are unconfirmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:51:59 -04:00
ajandClaude Fable 5.1 8f27eac2ca feat(cutting): plan each scrap cut-off segment where the route passes it
Plates with staged automatic scrap cut-offs could not be planned: P260810-01
plate 1 ran out of budget even at 10x effort, while the same plate plans
Ready in 3.6k expansions without the lines. A cut-off was one fixed program
cut whole before every part its line passes through, which dragged the tour
to the middle of the sheet and left the head at the far edge of the line;
and cutting every line first (the rejected alternative) weakens the sheet
and breaks the containment operators rely on when they fast-forward a
changed program.

A cut-off stays one definition, but Plate.RegenerateCutOffs now makes one
part per trimmed segment, each with its own place in the cut sequence.
CutOffSegments gives each segment the span of the nominal line it stands
for (end of the segment before it to start of the one after), and the
dependency rules of the planner and Part Sequencing use that span, so a
segment precedes only the parts beside it. The free-order search plans the
parts as if the segments were not there, so adding lines no longer changes
a plate's route, then CutOffInsertion slides each segment into the latest
gap before its first part where the rapid into its pierce and the rapid
from its end cross nothing cut before; only when one fits nowhere is the
plate searched with the segments among the parts. The search looks through
segments wherever they sit (entries face the next part, hole routes rank
from the pre-segment position, rapids are still checked from the segment's
end), so an applied plan replanned with the order kept replays Ready.
Segments saved out of consecutive order are recorded per segment in the
.nest file (`segments`); older readers load them consecutively.

P260810-01 plate 1 now plans Ready in 3639 expansions with every check
passing; rapid travel 1836 against 1547 without the lines. Plates without
cut-offs keep their PlanningCharacterizationTests digests, and the
problem-nest corpus has no plate below its baseline.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 11:38:38 -04:00
ajandClaude Opus 5.5 0ca4e64189 fix(bestfit): drop saved pair sets whose canonical frame changed
Saved best-fit pairs are in the drawing's canonical frame, but the canonical
angle is recomputed on load and was not saved with the set. When that
computation changes (RotatingCalipers checking every hull edge, marks no
longer counted as material), old pairs were replayed in a turned frame and
could overlap.

Each saved set now records canonicalAngle, and BestFitCacheStore.Import
skips sets whose angle no longer matches the recomputed one (1e-9 rad,
modulo a full turn). Sets from older files have no angle and are dropped;
their pairs are recomputed when next needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:04:23 -04:00
ajandClaude Opus 5.5 a7c0747e11 feat(planning): tab parts the laser support grid cannot hold level
Model the machine's support bed as a grid of contact points measured
from the plate origin, and check each part's center of gravity against
the convex hull of the grid points under its material. The bed is
extensible so straight and CAD-imported curved slats can follow.

- Core: SupportBed/SupportGrid, SupportMaterial and SupportAnalyzer
  (margin with registration tolerance, best single tab point)
- Nest-wide support bed that every plate follows, saved in
  plateDefaults.supportBed; machine defaults in support-grid.json
- CuttingParameters.SupportTabs: Plan Cutting tabs only unsupported
  parts and limits their perimeter entries to points where the tab
  steadies the part, falling back to any fitting entry
- Desktop: Nest > Support Grid..., View > Draw Support overlay and a
  "Tab parts the support grid cannot hold level" tab option
- docs/support-tabs.md, nest file format and planner notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 16:55:24 -04:00
ajandClaude Opus 5.5 d35178c3b5 feat(clamps): edit clamps on the plate view, on any sheet edge
Clamps could only sit on the origin-side edge and be edited as a list in
Nest > Clamps. Setting them up against a real sheet is easier by dragging
them where they are, so Plate > Edit Clamps adds an edit mode: drag a clamp
along its edge or onto another edge, click near an edge to add one, click a
clamp's locating dimension to type its distance from the origin, Delete to
remove. Dimensions measure to the clamp center or its near edge
(remembered per user). Edits replace the nest's rail so every sheet follows,
like the dialog.

Rail clamps now carry a ClampEdge (origin side, far side, origin end, far
end), positioned from the origin along that edge. Origin-side clamps keep
their ids and files; other edges persist in optional per-edge arrays in
.nest files and clamps.json, omitted when empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:09:04 -04:00
ajandClaude Opus 5.5 f009fab31d feat(ui): make color schemes app-wide and keep only hand-picked drawing colors
- Add Drawing.HasCustomColor, set when a color is picked in Drawing
  Properties or comes from a library record with a stored color.
- Persist it as customColor in .nest files (omitted when false) and keep it
  out of drawing packages; split pieces inherit it.
- Recolor non-custom drawings from the active palette when a nest opens and
  when the scheme changes; custom colors keep their palette slot.
- Library settings store a color only when it is custom.
- Document drawing colors in nest-file-format.md and nesting-view-colors.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 20:24:33 -04:00
ajandClaude Opus 5.5 d87302a189 feat(cad): exclude entities and set cut/etch/display-only roles
Select entities in the CAD converter (click, or drag a window/crossing
box) and right-click to exclude/include them or set them to cut, etch or
display only, regardless of their DXF layer. Delete excludes the
selection.

Roles are stored per source entity on the drawing and persisted in the
entity set as an optional `roles` map. Display-only geometry is kept out
of the program, so posts, nesting and verification never see it; placed
parts draw it as a dashed overlay. Load-time scribe repair skips
etch-layer entities set to cut. Per-entity exclusions now survive filter
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 05:14:55 -04:00
ajandClaude Opus 5.5 ba39c3dc55 feat(cad): add tabbed cutouts with draggable tabs
A hole can now be declared a tabbed cutout in the CAD Converter: pick a
circle or any edge of a closed hole, set the tab count and width, and drag
the tabs around the hole. The definition persists on the drawing and in the
nest file, and stays editable from the source entities.

The builder replaces the hole with open cut pieces and uncut spans on a new
Tab layer. Tabs are material, so area, nesting and overlap checks still see
a closed hole, but they are never cut. Plan Cutting and Assign Lead-ins cut
each piece from the slug with the internal lead-in and no lead-out. A lead
that would reach back over a tab is mirrored, and an arc lead onto an arc
pierces straight in. Planning accounts for each span between tabs and runs
the open-cut proof, so its own tabbed cutouts certify; tabs followed by a
lead-out are still refused. Pre-post review flags programs that still
contain tab motions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 00:50:11 -04:00
ajandClaude Opus 5.5 42d99f296c feat(sequencing): add per-drawing Cut first priority
Drawings flagged CutFirst (e.g. test squares) are sequenced ahead of
every other part: they get their own route with the chosen method, then
the remaining parts follow. Cutoffs crossing a cut-first part still
precede it. The flag is editable in Edit Drawing, persists in .nest as
`cutFirst` (omitted when false) and is kept by split pieces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 22:19:03 -04:00
ajandClaude Opus 5.5 6f555345e3 feat(cad): cut breakoff joining lines as tabbed open break cuts
Breakoffs are now picked by their joining line, default to the smaller side as scrap
(flippable), and cut that line between tabs on a new Break layer instead of slots.
Plan Cutting and Assign Lead-ins enter each cut from the scrap with the nest's internal
lead-in, or pierce on the line without one; checks treat break cuts as intentional open
cuts that are not material.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:46:35 -04:00
ajandClaude Opus 5.5 6fd14c5452 feat(cad): tab breakoff scrap to the part in the CAD Converter
Forming backgauge tabs drawn as open cuts came off the sheet loose and could not be
planned. A breakoff merges the scrap into the outline and cuts the shared edge as
closed slots between tabs, so it stays attached and every pierce lands in scrap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:42:37 -04:00
ajandClaude Opus 5.5 0f06748f28 feat(io): persist pins and pins-off
Drawing cut pins and the per-placement pins-off flag now survive
save/reload and travel in drawing packages.

- `DrawingDto.cutPins` reuses the plate cutting-parameter lead and tab
  DTOs. Pins are read strictly per entry: an unknown lead or tab type is
  never replaced by a default, because a pin that cut differently from
  what was saved is worse than no pin. A bad entry is skipped with a
  reader warning naming the drawing and pin, and the rest load; a value
  that is not a list loads no pins, with a warning. A nest never fails to
  open over pins.
- `PartDto.drawingPinsOff` is written only when true. Anything other than
  a boolean loads as pins on (the drawing wins, D1) with a warning naming
  the plate and part, so an operator's override is never lost silently.
- Packages carry pins as drawing content, so pinning a drawing changes
  its package hash and the newer-revision notice can tell geometry/pins
  changes from detail-only ones.
- `Part.DrawingPinsOff` holds the flag; nothing plans with it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:33:21 -04:00
ajandClaude Opus 5.5 15654c0690 feat(core,io): drawing library link
A nest drawing copied from the drawing library has to remember exactly which
revision it is a copy of, so later slices can tell when the library moved on
(D2b) and so a save can target the right drawing. `Drawing.Library` holds the
server key, id, revision and a snapshot of that revision's cut type, thickness,
material, grade, units, notes and package hash. It is immutable, so copies can
share it.

`DrawingDto.library` is additive and omitted when null. It is read leniently:
anything that cannot be trusted as a link drops the link with a reader warning
and the drawing still loads, because a nest must never fail to open over
provenance. Enum names are checked as single names since Enum.TryParse would
accept numbers and comma lists.

Copy paths: converter edits update drawings in place and so keep the link;
canonical engine copies carry it; split pieces are new geometry and get none.
Packages strip it so linking never changes a drawing's package bytes or hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:33:17 -04:00
aj d9273c889f fix(io): keep a sheet's own clamp rail and its removals through save and reload
Commit can attach the stock's rail to a sheet whose nest has no (or another)
default rail, but only the default rail was saved and the reader re-attached
sheets only to it, so the removed clamp and its time vanished on reload. A
sheet following a rail other than the default now saves it in an optional
per-plate clampRail (read leniently, omitted otherwise), and the reader
re-attaches the sheet to it.

PM 22daec3d-dabb-4aae-95f7-c1d090d5dcdf
2026-10-13 18:15:50 -04:00
aj 04d538b0a4 fix(io): omit removedClamps from sheets without a removal
Every plate was written with "removedClamps": [], so files without the
feature changed. The key is now omitted unless a clamp was removed, like
the other additive sections; nest-file-format.md documents removedClamps
and removalMinutes and their lenient reading.

PM 6ded8ac4-84fd-4e8b-9a3b-6f41e494542c item (c)
2026-10-13 18:15:50 -04:00
aj 53db543d6b fix(io): read a malformed removedClamps value without failing the nest
The optional per-plate removedClamps list was deserialized as a typed
List<string>, so a string or a list of numbers threw JsonException and the
whole nest failed to open. It now reads leniently: a non-array value reads
as none removed and non-string entries are skipped. The saved zones load
either way, so a removed clamp's area is never silently re-blocked or lost.

PM 9f3d676b-8a1c-4d9a-b877-b5e01e70c3f0
2026-10-13 18:15:49 -04:00
aj ec058d270c feat(core): remove individual rail clamps on one plate
Slice 3b task 1 (D8/D9): ClampRail gains RemovalMinutes (D9, default
5 minutes, validated finite >= 0) and Plate gains RemovedClamps (rail
clamp ids taken off this sheet as a last resort) plus the computed
ClampRemovalMinutes. Plate.SetClampRail gains an overload that
attaches a rail with a removed-clamp set; zones become the rail's
resolution for the sheet minus those ids, re-resolved on every
resize/rotate so a removed clamp stays off and reappears removed if a
later resize brings it back onto the sheet. ClampRail.Matches gains an
overload comparing against the reduced resolution.

IO: PlateDto.RemovedClamps and ClampRailDto.RemovalMinutes persist
(missing removalMinutes falls back to the default; missing
removedClamps means none). The writer's empty-plate drop logic no
longer drops a sheet whose only content is a removal state the
defaults can't recreate. The reader re-attaches the nest rail to a
plate only when its saved zones equal the rail's resolution minus its
saved removed ids, so a reduced-clamp sheet keeps following the rail
(and the removal) through later resizes instead of freezing as static
zones.

Engine/desktop wiring (minimal-removal computation, Auto Nest
last-resort removal, cut-time, UI) is Slice 3b tasks 2-3, not yet
done.

Tests: 16 new Core (ClampRailTests) + 5 new IO (ClampRailPersistenceTests)
covering validation, resize/resolve behavior, round-trip, legacy
missing-key defaults, and the empty-plate-with-removal-only retention
case. Mutation check (reverting the RemovedClamps subtraction) turns
3 Core + 2 IO assertions red, confirming real coverage.

Gates: Release OpenNest.Tests 3884 passed/31 skipped, OpenNest.IO.Tests
105 passed, OpenNest.Engine.Tests (Clamp filter) 98 passed; scoped
dotnet format --verify-no-changes clean.
2026-10-13 18:15:49 -04:00
aj 1e37c661ff fix(io): harden clamp rail persistence
Omit an empty clamp-carrying plate only when a new plate from the saved
defaults would recreate it: same size and quadrant and zones the saved
rail recreates. A rail-following empty sheet with another size or
quadrant is written again instead of being lost.

Read plateDefaults.clampRail through a lenient converter so a wrongly
typed rail (a string or null width, non-numeric centers, an array in
place of the object) loads as no rail while every plate's saved zones
still load, as the file format documents, instead of failing the whole
nest.

Review findings 4 and 5, Project Memory 4e5f566d.
2026-10-13 18:15:49 -04:00
aj 5674764447 feat(io): persist the nest clamp rail
Write the nest's machine clamp rail under plateDefaults.clampRail
(width, depth, centers) so plates created or resized after a reload
still get the clamps. The key is omitted for nests without a rail, and
a missing, empty or malformed rail loads as none while each plate's
saved zones still load.

On load a plate follows the rail again only when its saved zones are
exactly the rail's for that sheet; other zones are kept as written.
The empty-plate filter now drops an empty sheet whose zones the saved
rail recreates, so the editor's trailing empty sheet is not written
for every clamped nest; empty sheets with other zones are still kept.
2026-10-13 18:15:49 -04:00
aj d001abcf69 feat(io): persist clamp zones in nest files
Add the additive per-plate clamps array (id, x, y, length, width) to the
nest DTO with writer and reader support. Files without the key load with
zero zones; an individually malformed entry is dropped while the rest of
the nest loads. A plate carrying only clamp definitions now survives the
writer's empty-plate filter; genuinely empty plates remain omitted.
Format documented in docs/nest-file-format.md.
2026-10-13 18:15:49 -04:00
aj ef2f9aa714 feat(core,io): persist nest status and made-by in nest files
Add NestStatus (Quote/ToBeCut/HasBeenCut) plus MadeBy on Nest, written as
additive camelCase nest.json fields with PascalCase enum strings matching
the units convention. Legacy files and unknown status values fall back to
Quote. The nest info dialog gains a Status dropdown and Made By box.
2026-09-29 16:21:17 -04:00
aj b254a0bd3e feat(io): persist part cutting programs and plate parameters 2026-09-28 19:17:59 -04:00
aj 4da48eed68 fix(core): keep cut-offs in their cut sequence through regenerate and save
A cut-off's place in Plate.Parts is its cut sequence number, but
RegenerateCutOffs removed every cut-off part and appended it again, so
any part drag, fill or cut-off move sent the cut-offs to the end. The
nest file didn't store the position either, so reopening did the same.

RegenerateCutOffs now puts each cut-off back at its previous index (new
cut-offs go at the end), and CutOffDto.Sequence saves the index. Older
files without it load the cut-offs at the end, as before.
2026-09-28 18:44:04 -04:00
aj aec0523062 style: apply CSharpier formatting to all C# sources
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.
2026-09-20 16:41:50 -04:00
ajandClaude Opus 4.6 c1e6092e83 feat: add entity-based suppression with stable GUIDs
Entities now have a Guid Id for stable identity across edit sessions.
Drawing stores the full source entity set (SourceEntities) and a set of
suppressed entity IDs (SuppressedEntityIds), replacing the previous
SuppressedProgram approach. Unchecked entities in the converter are
suppressed rather than permanently removed, so they can be re-enabled
when editing drawings again.

Entities are serialized as JSON in the nest file (entities/entities-N)
alongside the existing G-code programs. Backward compatible with older
nest files that lack entity data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:25:48 -04:00
ajandClaude Opus 4.6 ffd060bf61 feat: serialize plate optimizer settings in nest files
Add PlateOptions and SalvageRate properties to the Nest class and
round-trip them through NestWriter/NestReader via a new PlateOptionDto.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 00:38:02 -04:00
ajandClaude Opus 4.6 9db7abcd37 refactor: move material and thickness from Plate to Nest
Material and thickness are properties of the nest (all plates share the
same material/gauge), not individual plates. This moves them to the Nest
class, removes them from Plate and PlateSettings, and updates the UI so
EditNestInfoForm has a material field while EditPlateForm no longer shows
thickness. The nest file format gains top-level thickness/material fields
with backward-compatible reading from PlateDefaults for old files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 21:00:59 -04:00
ajandClaude Opus 4.6 c9b5ee1918 feat: serialize HasManualLeadIns, LeadInsLocked, and :SUPPRESSED in nest files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 13:29:49 -04:00
ajandClaude Sonnet 4.6 dd2892a9fe feat: serialize/deserialize bends in nest file format
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 20:22:55 -04:00
ajandClaude Opus 4.6 b970629a59 feat: add material library resolution, assist gas support, and UI fixes
- Add MaterialLibraryResolver for Cincinnati post processor to resolve
  G89 library files from material/thickness/gas configuration
- Add Nest.AssistGas property with serialization support in nest format
- Add etch library support with separate gas configuration
- Fix CutOff tests to match AwayFromOrigin default cut direction
- Fix plate info label not updating after ResizePlateToFitParts
- Add cutoff and remnants toolbar button icons

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 22:00:57 -04:00
ajandClaude Opus 4.6 2f19f47a85 feat: serialize/deserialize cut-off definitions in nest file format
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 19:40:01 -04:00
ajandClaude Sonnet 4.6 d7cc08dff7 refactor: rename .opnest file extension to .nest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 08:29:46 -04:00
ajandClaude Opus 4.6 d2eeb23107 refactor(io): migrate nest file extension from .zip to .opnest
Add FileExtension and FileFilter constants to NestFormat and update all
references across Console, MCP, Training, and WinForms projects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 07:26:04 -04:00
ajandClaude Opus 4.6 d6ffa77f35 feat(console): improve training data collection and best-fit persistence
- Add verbose per-file and per-sheet-size console output during collection
- Skip already-processed parts at the sheet-size level instead of all-or-nothing
- Precompute best-fits once per part and reuse across all sheet sizes
- Clear best-fit cache after each part to prevent memory growth
- Save best-fits in separate bestfits/ zip entries instead of embedding in nest.json
- Filter to Keep=true results only and scope to plate sizes in the nest
- Set nest name to match filename (includes sheet size and part count)
- Add TrainingDatabase with per-run skip logic and SQLite schema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 12:39:24 -04:00
ajandClaude Opus 4.6 7e0edd112a refactor: use 'length' instead of 'height' in nest JSON format
Rename SizeDto.Height to SizeDto.Length so the serialized JSON
uses "width"/"length" which is more natural for plate materials.
The core Size struct still uses Height internally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:50:53 -04:00