Files
OpenNest/docs/nest-file-format.md
T
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

402 lines
24 KiB
Markdown

# Nest file format and saved cutting state
`.nest` is a ZIP archive with additive version-2 JSON metadata in `nest.json`.
Old files remain readable. Older applications ignore the new cutting-state fields;
opening and saving with an older application loses that state.
## Job metadata
`nest.json` carries the workflow fields shown in the nest info dialog:
`name`, `customer`, `dateCreated`, `dateLastModified`, `notes`, `material`,
`thickness`, plus `status` (`"Draft"`, `"Quote"`, `"ToBeCut"`, `"HasBeenCut"`) and `madeBy`.
Status and made-by are additive: files written before them load as Quote with an
empty maker, and an unrecognized status value likewise falls back to Quote. Builds
before `Draft` therefore open a Draft nest as Quote, and saving it there keeps Quote.
`machine` optionally names the machine the nest is cut on: `id` (its machine
configuration's GUID) and `name` (the configuration's name when assigned, so the nest
still says what it was made for on a PC without that configuration). The key is omitted
when no machine is assigned. A non-object, a missing, non-string, unparseable or empty
`id` loads as no machine; a missing or non-string `name` loads as empty.
## Programs and placements
- `programs/program-N` is drawing N's clean G-code program; optional
`programs/program-N-subs` contains its hole sub-programs.
- Each serialized plate has a one-based `id`. Its `parts` array excludes cut-offs;
each placement retains `drawingId`, `x`, `y`, and `rotation` (radians).
- A placement with `HasManualLeadIns` also writes `parts/plate-P/part-K`, and
`parts/plate-P/part-K-subs` when it has hole sub-programs. P is the serialized
plate ID; K is the zero-based index in that plate's serialized parts array.
Empty plates and cut-offs do not consume these part indices.
- The placement's `program` field references that ZIP entry. Its G-code is saved
in the already-rotated local part frame, with the placed origin at `(x, y)`.
Existing `:LEADIN`, `:LEADOUT`, and other layer tags, tab gaps, and G65 calls
are retained rather than regenerated by a cutting strategy during load.
Hole IDs may be negative: they are identifiers, not validity sentinels.
- `drawingHash` protects against restoring obsolete cutting paths after an
externally edited drawing. It is uppercase SHA-256 of UTF-8 text composed of
the main text's decimal UTF-16 character count, `:`, main text, and sub-program
text (empty when absent). The hash includes both drawing entries so changing a
hole alone invalidates the placed cutting program. Text is read without a BOM;
otherwise whitespace and line endings participate in the hash.
The reader constructs and places a clean part first, then installs its saved
program without rotating it again. The drawing reference remains authoritative
for Remove Lead-ins: removing them restores the clean drawing at the saved pose.
Successful restoration marks the part as carrying lead-ins and restores its
`leadInsLocked` flag. The per-part `CuttingParameters` reference remains null;
the saved program, not strategy regeneration, determines the cutting geometry.
Missing `program` references in legacy files load clean and silently, even when
old transient flags were true. A missing or mismatched drawing hash likewise
loads clean silently. A referenced but missing, unparseable, or motionless part
program, or an unresolved/empty hole sub-program, leaves that part clean and
unlocked without discarding the rest of the nest. `NestReader.Warnings` identifies
the plate, zero-based part index, and entry; File > Open displays these warnings.
The program reader is not a general G-code or geometric safety validator; the
saved cutting geometry is not validated against the drawing.
Nests converted from PEP before their drawings carried etch marks keep those
marks only in the saved placed programs. When a drawing has no etch (scribe)
moves and every placement with a saved program carries the same marks, the
reader adds those marks to the drawing in its own frame (after its existing
moves) and refreshes that drawing's clean placements, so Remove Lead-ins and
Clear Lead-ins & Cut-offs keep the marks. Saved programs are never changed. If
the saved programs disagree, the drawing is left without marks and no warning
is raised. The drawing hash is checked against the file text before this
repair, so saved programs still load; the next save writes the marks into
`programs/program-N`.
## Cut-offs
Each serialized plate's `cutOffs` array holds its cut-off definitions: `x`, `y`,
`axis` (`vertical` or `horizontal`), optional `startLimit` and `endLimit`, and the
places in the plate's cut sequence (`Plate.Parts` order, cut-offs included) of the
parts the definition materializes. Loading regenerates each definition with the
reader's default cut-off settings and makes one part per trimmed segment. `sequence`
is the zero-based place of the first segment (by position along the line); null or
absent in older files, which load the cut-off at the end. `segments` lists the place
of every segment in ascending order along the line and is written only when the
segments are not consecutive, as after Plan Cutting cuts a strip line in pieces where
the route passes them. Readers without `segments` place the segments consecutively
from `sequence`, which is still a dependency-safe order. A `segments` list whose length
differs from the regenerated segment count (nondefault cut-off settings, a moved part)
places the segments consecutively from its smallest entry. `headLockOvercut`
(model units) and `farEdgeFirst` record a head-locked overcut past the far sheet
edge and whether its segment starts at that edge (see
[automatic cutoffs](automatic-scrap-cutoffs.md#head-locked-overcut-at-the-far-edge));
both are omitted when zero or false, and older files load without them.
## Plate cutting parameters
Each plate's optional `cuttingParameters` stores the settings used by Assign
Lead-ins and Place Lead-in. Missing settings stay null, preserving the old default
selection behavior. `OpenNest.IO.CuttingParametersSerializer` owns the shared
cross-platform DTO mapping; the desktop settings serializer delegates to it so
existing saved settings keep their JSON contract. Settings are independent of
the saved part programs: restoring settings never regenerates those programs.
Lead settings are external (the perimeter) and internal (every cutout, full
circles included). Older files and desktop settings also stored
`arcCircleLeadIn`/`arcCircleLeadOut`; these are ignored on read and no longer
written, so circles in a re-planned older nest take its internal leads. Saved
part programs keep the leads they were cut with.
## Clamp zones
Each plate may carry an optional `clamps` array of fixed rectangular keep-out
regions in plate coordinates: `id`, `x`, `y`, `length` (X extent) and `width`
(Y extent, the depth intrusion from the `y = 0` operator-side edge for machine
clamps). Zones are exclusion and display data only - never material, quantity,
utilization, cut sequence or CNC output. Files without the key load with no
zones, and an individually malformed entry (blank id, non-positive size) is
dropped while the rest of the nest loads.
`plateDefaults.clampRail` optionally stores the nest's machine clamp rail:
`width` (along the edge), `depth` (into the sheet) and `centers` (X positions
measured from the plate origin along the origin-side edge). Clamps on the other
edges are listed in optional `farSideCenters` (X, along the edge across the
sheet's width from the origin), `originEndCenters` (Y, along the edge through
the origin) and `farEndCenters` (Y, along the edge across the sheet's length);
each is omitted when empty, so origin-side-only rails are written exactly as
before. Rail clamp ids (`clamp-N`) number the origin-side clamps first, then
far side, origin end and far end, each by ascending center. Older readers
ignore the extra arrays; a plate's saved `clamps` then no longer match their
rail and load as explicit zones. New plates follow
the rail and resolve it for their own size and quadrant; clamps wholly beyond a
sheet are skipped and one still reaching onto it keeps its whole footprint, so
a zone may extend past the sheet edge. The key is omitted
when a nest has no rail, so such files are unchanged; a missing, empty or
malformed rail loads as none. On load a plate follows the rail again only when
its saved `clamps` are exactly the rail's zones for that sheet; any other saved
zones are kept as written.
The rail's optional `removalMinutes` is the time to take one clamp off and put
it back; a missing or invalid value loads as five minutes. A plate on which
clamps were removed (by Auto Nest as a last resort, or by hand) lists their rail ids in an optional
`removedClamps` array (omitted when none were removed); its `clamps` already
exclude them, and it follows the rail minus those ids. A malformed
`removedClamps` value reads as none removed (non-string entries are skipped);
the saved `clamps` load either way. A plate that follows a rail other than the nest's default
saves it in its own optional `clampRail` (same shape), so its removal state and
time survive a reload; plates following the default rail omit it.
An empty plate (no parts, no cut-offs) is omitted from the file unless that
would lose clamp zones: it is omitted like any other empty plate only when a
new plate from the saved defaults would recreate it (same size, quadrant, edge
and part spacing, zones the saved rail recreates), which keeps the editor's trailing empty sheet
out of the file while it matches the defaults. Nest-defaults edits move only an
untouched trailing empty, cutoff-free sheet to the new size, quadrant, edge and
part spacing, re-resolving its rail. It must have exactly matched the old defaults
and followed their rail with no removed clamps or explicit zones (or have had no
rail and no zones when the defaults had none). Edited sheets, middle empty sheets,
and sheets with parts, cut-offs, removed clamps or explicit zones are not changed.
An empty plate with other zones, or a rail-following one whose size, quadrant or
spacing differs from the saved defaults, is still written.
## Stock item codes
A plate may carry an optional `stockItemCodes` array: the inventory item (a
MaterialLedger item code) each physical sheet of its `quantity` is cut from, in
sheet order. Entry *i* names sheet *i*; sheets past the end are uncoded, and a
bundle used for several sheets repeats its code. It is written only when at
least one sheet is coded, so files without coded plates are unchanged. A
missing or malformed value loads as no codes; non-string entries are skipped,
a blank entry ends the coded sheets, and entries beyond `quantity` are dropped.
Older readers ignore the key.
Codes are hand-off data for the inventory system, never quantity accounting.
The model drops them when they stop describing the sheet: resizing a plate
(other than turning it) clears its codes, lowering its quantity truncates them,
and changing the nest's material, grade or thickness clears every plate's.
Auto Nest assigns them only to sheets it nests on ledger stock.
## Expected remnants
Each written plate may carry a derived `expectedRemnant`: `{ "width", "length",
"source" }`, the rectangular drop it is expected to leave, in model units with
`width` the shorter side. `NestWriter` computes it at every save from the current
layout with Core's `PlateRemnant.Expected`, for readers that have no geometry
(OpenNest.Server's `/sheets`). It is omitted when the plate leaves none. Readers
skip it whatever its shape and never load it into the model, so a value edited by
hand or left by an older layout cannot go stale.
- `"cutoff"`: a full-span cut-off (no limit short of either sheet edge) frees
material no part reaches into. The drop runs from the line's nominal position
to the sheet edge or the next such line, across the whole sheet, less the
plate's kerf (`cuttingParameters.kerf`, zero without parameters) at each
bounding line. A head-locked overcut uses the nominal position. It wins over a
larger edge offcut; the largest such piece is reported.
- `"offcut"`: otherwise, the largest full-width or full-length strip of the
work area clear of the parts' material bounds by the part spacing, exactly the
offcut whole-job salvage credits. Cut-off parts and marks never bound material.
Both sides must reach the nest's `minimumRemnantDimension` (model units; Auto Nest
records the minimum remnant size it last used, and older files load 12). A cut-off
drop below it falls through to the offcut. Interior holes and irregular leftovers
are not reported.
## Support grid
`plateDefaults.supportBed` optionally stores the machine support bed every sheet of
the nest rests on (see [support tabs](support-tabs.md)): `type`, then for `grid`
`pitchX`, `pitchY`, `offsetX`, `offsetY` (the first grid point, measured from the
plate origin into the sheet), or for `slats` `axis` (`x` or `y`, the axis the slats
run along), `pitch`, `thickness` and `offset` (the first slat's center line); both
end with `registrationTolerance` and `stabilityMargin`. All lengths are in the
nest's units. The key is omitted when the nest has none, so such files are
unchanged. A missing pitch or thickness, a non-numeric value, an unknown `type` or
`axis`, or an invalid value (a non-positive pitch, a negative thickness, tolerance
or margin) loads as no bed; the rest of the nest loads. Versions without slat
support read a `slats` bed as none. Every plate follows the nest's bed.
## Drawing edits
`DrawingProgramSnapshot` captures serialized main and hole-program text before
the converter is loaded. After an accepted edit, only parts referencing drawings
whose program text changed are rebuilt (lead-ins, tabs, and locks cleared); their
location and rotation remain unchanged. Reference identity, not editable names,
keys this comparison. The desktop invalidates the affected layout graphics.
Name, quantity, and color edits leave placed programs alone. The properties
editor previously called `LayoutPart.Update`, a color refresh, not `Part.Update`;
it now explicitly refreshes only the edited drawing's layout colors.
## Saved best-fit pairs (cache only)
Optional `bestfits/bestfit-N` entries hold drawing N's cached best-fit pair
results: a JSON array of sets, each with `plateWidth`, `plateHeight` and
`spacing` (the plate size and part spacing it was filtered for),
`canonicalAngle` and `results`. Each result has `part1Rotation`,
`part2Rotation`, `part2OffsetX`, `part2OffsetY`, `strategyType`, `testNumber`,
`candidateSpacing`, `rotatedArea`, `boundingWidth`, `boundingHeight`,
`optimalRotation`, `keep`, `reason`, `trueArea` and `hullAngles`. Angles are
radians; pair geometry is in the drawing's canonical frame.
The canonical angle is not saved with the drawing: readers recompute it from
the program, so a change to that computation turns the frame of drawings saved
before it. `canonicalAngle` records the drawing's canonical angle when the set
was saved, and `BestFitCacheStore` drops a set on load unless it matches the
recomputed angle (within 1e-9 radians, modulo a full turn), since pairs replayed
in a turned frame can overlap. Sets from files saved before the field existed
have no angle and are dropped the same way; their pairs are recomputed when
next needed.
The entries are only a cache: readers may ignore them and writers may omit
them, at the cost of recomputing pairs. Writers save only sets whose
(width, length, spacing) matches a plate in the nest, and only kept results; a
drawing without such a set gets no entry. `NestReader` and `NestWriter` touch
these entries only when given an `IBestFitStore` through `BestFitStore`;
without one, a reader skips them unparsed and a writer writes none. The front
ends pass the engine's `BestFitCacheStore.Shared`, which adds loaded sets to
the process-wide `BestFitCache` without replacing sets already computed.
## Drawing library links
A drawing copied from a server drawing library carries a `library` object:
`serverKey` (the normalized server URL), `id`, `revision`, and a snapshot of that
revision's `cutType`, `thickness`, `materialName`, `materialGrade`, `units`, `notes`
and `packageSha256`. It is omitted for other drawings. Read leniently: a value that
is not an object, has the wrong types, a blank server key, an empty id, a revision
below 1, a cut type or units that are not one supported name, or a negative
thickness loads the drawing without a link and adds a reader warning. Converter
edits keep the link as provenance; split pieces get none. Drawing packages leave
the link out, so linking a drawing does not change its package bytes.
### Drawing packages
A library drawing's geometry travels as a canonical drawing package
(`OpenNest.IO.DrawingPackage`): a `.nest` archive with one drawing and no plates,
fixed archive timestamps and dates, and no best-fit entries, so equal content has
equal bytes and SHA-256. The server stores each distinct package once by that hash.
A package holds geometry and geometry-bound data only: program, source entities and
offset, suppressed entity IDs, entity roles, bends, cut pins, breakoffs, tabbed cutouts and material
density. Fields
the server's library record owns are written as their defaults, so editing them never
changes the hash:
- nest and drawing `name` (blank), drawing `customer`, material `name` and `grade` (blank);
- `color`, `customColor`, `priority`, `cutFirst` and `constraints` (omitted);
- `quantity.required` (zero) and `library` (omitted).
Adding a library drawing to a nest ignores whatever a package holds for these fields
and applies the record's values.
An unchanged DXF file imports with the same entity ids, but from another folder its
package records another `source.path`, and packages saved before entity ids were stable
carry random ones, so the packages' hashes differ. `DrawingPackage.GeometryKey`
compares geometry instead: the SHA-256 of the package entries with `source.path` left
out and every GUID replaced by its order of first appearance. BOM import to a set uses
it to reuse a library drawing whose geometry has not changed.
## Drawing colors
`color` is the drawing's display fill. A drawing writes `customColor: true` when
someone picked that color on purpose (Drawing Properties, or a library record with a
stored color); it is omitted when false. Opening a nest in the desktop app, or
changing the color scheme, reassigns every non-custom, non-cutoff drawing a color
from the active scheme's palette, so the view follows the application's scheme
rather than whichever scheme the file was saved under. Files written before this
flag have no custom colors and are recolored on open. Split pieces keep the flag.
## Drawing cut-first flag
A drawing may write `cutFirst: true` to have automatic part sequencing cut its parts
before every other part (see [part sequencing](automatic-scrap-cutoffs.md#part-sequencing));
it is omitted when false, so older readers ignore it. Split pieces keep the flag.
## Drawing cut pins
A drawing may carry a `cutPins` array, omitted when empty. Each pin has `x`/`y`
in the drawing's source frame (the frame of its source entities, which does not
move when the program is re-origined at its first rapid; the program frame is the
source frame minus `source.offset`), a `leadIn` and `leadOut` in the same shape as
plate cutting parameters, and an optional `tab` omitted for a closed contour.
Pins are part of the drawing's content, so drawing packages carry them and they
change the package hash. Pins are read strictly per entry but leniently per file:
an entry that is not an object, has wrong types, an unknown lead or tab type, an
invalid dimension, or a tab together with a lead-out is skipped with a reader
warning naming the drawing and pin; the other pins and the drawing load. A
`cutPins` value that is not an array loads no pins, with a warning. Converter
edits keep pins (they stay in the source frame); split pieces and canonical
engine copies get none.
A placement may write `drawingPinsOff: true` to ignore its drawing's pins on that
part only; it is omitted when false. Any value other than a boolean loads as
false (the drawing's pins apply) with a warning naming the plate and part.
## Drawing breakoffs
A drawing may carry a `breakoffs` array, omitted when empty. Each entry has
`edgeIds` (source entity IDs of the joining line), `flip` (omitted when false),
`tabWidth` and `tabCenters` (distances along the joining line), all in the source
frame; see [breakoffs](breakoffs.md). The stored program already has them applied,
so readers never rebuild geometry from them. An entry with no edges, a tab width that
is not positive and finite, or a nonfinite tab center is dropped with a reader
warning; the drawing and its program load unchanged, only the breakoff stops being
editable.
Break cuts are saved as motions tagged `:BREAK`. In a drawing program each break cut
travels with its scrap on the left; placed programs may cut it either way. Versions
without breakoff support cannot read the tag.
## Drawing tabbed cutouts
A drawing may carry a `tabbedCutouts` array, omitted when empty. Each entry has
`edgeIds` (source entity IDs of the hole's contour), `tabWidth` and `tabCenters`
(distances counterclockwise around the hole from its lowest-left vertex), all in the
source frame; see [tabbed cutouts](tabbed-cutouts.md). The stored program already has
them applied, so readers never rebuild geometry from them. An entry with no edges, a tab
width that is not positive and finite, or a nonfinite tab center is dropped with a reader
warning; the drawing and its program load unchanged, only the cutout stops being editable.
Tab spans are saved as motions tagged `:TAB`. They bound material but are never cut;
versions without tabbed cutout support read them as ordinary cut motions.
## Drawing entity roles
`entities/entities-N` holds drawing N's source entities, the IDs of suppressed
(excluded) ones in `suppressed`, and an optional `roles` object, omitted when empty,
mapping source entity IDs to `"cut"`, `"etch"` or `"display"`. An entity without an
entry keeps the role its layer implies: `ETCH`, `SCRIBE` and `ENGRAVE` etch, every
other layer cuts. The stored program already has the roles applied: etched entities
are `:SCRIBE` motions and display-only entities are not in the program at all, so
posts, nesting, area and verification never see them; the desktop draws them with
each placed part. Roles are geometry-bound data, so drawing packages carry them and
they change the package hash. Unknown role names and malformed IDs are skipped (that
entity keeps its layer role). Load-time etch repair only uses entities whose role is
etch, so an etch-layer entity set to cut stays cut. Split pieces get no roles.
Versions without role support ignore `roles`; they keep the program as saved but
lose the overrides, and display-only geometry, on the next converter edit.
## Verification and limits
The automated tests cover save/reopen/re-save, rotated/manual/locked parts,
lead-outs and tab gaps, hole binding, clean removal, stale drawings, damaged
entries isolated from other parts, plate settings, and CI Fiber posting before
and after reload. Drawing-change decision logic runs on Linux; desktop rendering,
dialog values, and clicks require Windows acceptance.
Known separate issue: the cutting strategy currently labels contour cuts
`Display`. CI Fiber includes them, but GravographIS skips Display. Saving restores
the existing layers; this change does not alter the strategy or either post.
Future hardening: make numeric G-code serialization/parsing culture-invariant
as a coordinated compatibility change; do not change just one side. Program
content deduplication is optional and must not change per-part ownership.
## Stock-option prices
The existing `plateOptions` entries retain width, length and `cost`; no new `.nest`
format version is required. Legacy zero cost means unspecified, not a free sheet.
Positive costs are generic per-sheet values in caller-consistent units. Solving
requires all available options to be priced or all to be unspecified.
Desktop and accepted MCP/API jobs retain offered options (API rows with zero available
quantity are excluded because `.nest` options do not retain inventory). MCP save also preserves
loaded job metadata, plate defaults and salvage settings, and accepted whole-job
requirements are stored on drawings. Options do not persist finite inventory or
associate a historical purchase price with each physical sheet. API quote archives
additionally retain request costs and validated proposal cost summaries; old archives
without summaries load with `Costs == null`.