Files
OpenNest/docs/superpowers/specs/2026-03-12-nest-file-format-v2-design.md
AJ Isaacs 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

5.3 KiB

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

{
  "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:329new NestReader(path)
  • MainForm.cs:363new NestReader(dlg.FileName)
  • EditNestForm.cs:212new NestWriter(Nest)
  • EditNestForm.cs:223new NestWriter(nst)
  • Document.cs:27new NestWriter(Nest)
  • OpenNest.Console/Program.cs:94new NestReader(nestFile)
  • OpenNest.Console/Program.cs:190new NestWriter(nest)
  • OpenNest.Mcp/InputTools.cs:30new NestReader(path)