mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-29 01:21:09 -04:00
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>
318 lines
19 KiB
Markdown
318 lines
19 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.
|
|
|
|
## 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`.
|
|
|
|
## 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.
|
|
|
|
## 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.
|
|
|
|
## Support grid
|
|
|
|
`plateDefaults.supportBed` optionally stores the machine support bed every sheet of
|
|
the nest rests on (see [support tabs](support-tabs.md)): `type` (`grid`, the only
|
|
layout so far), `pitchX`, `pitchY`, `offsetX`, `offsetY` (the first grid point,
|
|
measured from the plate origin into the sheet), `registrationTolerance` and
|
|
`stabilityMargin`, all in the nest's units. The key is omitted when the nest has
|
|
none, so such files are unchanged. A missing pitch, a non-numeric value, an unknown
|
|
`type` or an invalid value (a non-positive pitch, a negative tolerance or margin)
|
|
loads as no bed; the rest of the nest loads. 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.
|
|
|
|
## 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`.
|