Files
OpenNest/docs/superpowers/specs/2026-03-12-nest-file-format-v2-design.md
T
aj c2534ef08b feat: replace XML nest file format with JSON (v2)
Replace three separate XML metadata files (info, drawing-info,
plate-info) and per-plate G-code placement files with a single
nest.json inside the ZIP archive. Programs remain as G-code text
under a programs/ folder.

This eliminates ~400 lines of hand-written XML read/write code
and fragile ID-based dictionary linking. Now uses System.Text.Json
with DTO records for clean serialization. Also adds Priority and
Constraints fields to drawing serialization (previously omitted).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:44:43 -04:00

135 lines
5.3 KiB
Markdown

# Nest File Format v2 Design
## Problem
The current nest file format stores metadata across three separate XML files (`info`, `drawing-info`, `plate-info`) plus per-plate G-code files for part placements inside a ZIP archive. This results in ~400 lines of hand-written XML read/write code, fragile dictionary-linking to reconnect drawings/plates by ID after parsing, and the overhead of running the full G-code parser just to extract part positions.
## Design
### File Structure
The nest file remains a ZIP archive. Contents:
```
nest.json
programs/
program-1
program-2
...
```
- **`nest.json`** — single JSON file containing all metadata and part placements.
- **`programs/program-N`** — G-code text for each drawing's CNC program (1-indexed, no zero-padding). Previously stored at the archive root as `program-NNN` (zero-padded). Parsed by `ProgramReader`, written by existing G-code serialization logic. Format unchanged.
Plate G-code files (`plate-NNN`) are removed. Part placements are stored inline in `nest.json`.
### JSON Schema
```json
{
"version": 2,
"name": "string",
"units": "Inches | Millimeters",
"customer": "string",
"dateCreated": "2026-03-12T10:30:00",
"dateLastModified": "2026-03-12T14:00:00",
"notes": "string (plain JSON, no URI-escaping)",
"plateDefaults": {
"size": { "width": 0.0, "height": 0.0 },
"thickness": 0.0,
"quadrant": 1,
"partSpacing": 0.0,
"material": { "name": "string", "grade": "string", "density": 0.0 },
"edgeSpacing": { "left": 0.0, "top": 0.0, "right": 0.0, "bottom": 0.0 }
},
"drawings": [
{
"id": 1,
"name": "string",
"customer": "string",
"color": { "a": 255, "r": 0, "g": 0, "b": 0 },
"quantity": { "required": 0 },
"priority": 0,
"constraints": {
"stepAngle": 0.0,
"startAngle": 0.0,
"endAngle": 0.0,
"allow180Equivalent": false
},
"material": { "name": "string", "grade": "string", "density": 0.0 },
"source": {
"path": "string",
"offset": { "x": 0.0, "y": 0.0 }
}
}
],
"plates": [
{
"id": 1,
"size": { "width": 0.0, "height": 0.0 },
"thickness": 0.0,
"quadrant": 1,
"quantity": 1,
"partSpacing": 0.0,
"material": { "name": "string", "grade": "string", "density": 0.0 },
"edgeSpacing": { "left": 0.0, "top": 0.0, "right": 0.0, "bottom": 0.0 },
"parts": [
{ "drawingId": 1, "x": 0.0, "y": 0.0, "rotation": 0.0 }
]
}
]
}
```
Key details:
- **Version**: `"version": 2` at the top level for future format migration.
- Drawing `id` values are 1-indexed, matching `programs/program-N` filenames.
- Part `rotation` is stored in **radians** (matches internal domain model, no conversion needed).
- Part `drawingId` references the drawing's `id` in the `drawings` array.
- **Dates**: local time, serialized via `DateTime.ToString("o")` (ISO 8601 round-trip format with timezone offset).
- **Notes**: stored as plain JSON strings. The v1 URI-escaping (`Uri.EscapeDataString`) is not needed since JSON handles special characters natively.
- `quantity.required` is the only quantity persisted; `nested` is computed at load time from part placements.
- **Units**: enum values match the domain model: `Inches` or `Millimeters`.
- **Size**: uses `width`/`height` matching the `OpenNest.Geometry.Size` struct.
- **Drawing.Priority** and **Drawing.Constraints** (stepAngle, startAngle, endAngle, allow180Equivalent) are now persisted (v1 omitted these).
- **Empty collections**: `drawings` and `plates` arrays are always present (may be empty `[]`). The `programs/` folder is empty when there are no drawings.
### Serialization Approach
Use `System.Text.Json` with small DTO (Data Transfer Object) classes for serialization. The DTOs map between the domain model and the JSON structure, keeping serialization concerns out of the domain classes.
### What Changes
| File | Change |
|------|--------|
| `NestWriter.cs` | Replace all XML writing and plate G-code writing with JSON serialization. Programs written to `programs/` folder. |
| `NestReader.cs` | Replace all XML parsing, plate G-code parsing, and dictionary-linking with JSON deserialization. Programs read from `programs/` folder. |
### What Stays the Same
| File | Reason |
|------|--------|
| `ProgramReader.cs` | G-code parsing for CNC programs is unchanged. |
| `NestWriter` G-code writing (`WriteDrawing`, `GetCodeString`) | G-code serialization for programs is unchanged. |
| `DxfImporter.cs`, `DxfExporter.cs`, `Extensions.cs` | Unrelated to nest file format. |
| Domain model classes | No changes needed. |
### Public API
The public API is unchanged:
- `NestReader(string file)` and `NestReader(Stream stream)` constructors preserved.
- `NestReader.Read()` returns `Nest`.
- `NestWriter(Nest nest)` constructor preserved.
- `NestWriter.Write(string file)` returns `bool`.
### Callers (no changes needed)
- `MainForm.cs:329``new NestReader(path)`
- `MainForm.cs:363``new NestReader(dlg.FileName)`
- `EditNestForm.cs:212``new NestWriter(Nest)`
- `EditNestForm.cs:223``new NestWriter(nst)`
- `Document.cs:27``new NestWriter(Nest)`
- `OpenNest.Console/Program.cs:94``new NestReader(nestFile)`
- `OpenNest.Console/Program.cs:190``new NestWriter(nest)`
- `OpenNest.Mcp/InputTools.cs:30``new NestReader(path)`