mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 09:01:07 -04:00
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>
402 lines
24 KiB
Markdown
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`.
|