zudo-panel-designer docs
GitHub repository

Type to search...

to open search from anywhere

PanelConfig Format

PanelConfig is the versioned JSON envelope the editor downloads as the panel order artifact. It is produced by serializePanelConfig() and consumed by parsePanelConfig(), both exported from @zpd/core (source: packages/core/src/serialize.ts).

Envelope shape

{
  "version": 5,
  "app": "zpd",
  "panel": { "hp": 12, "widthMm": 60.6, "heightMm": 128.5 },
  "palette": ["black", "gold", "white"],
  "layers": [
    { "kind": "pcb-layer", "id": "pcb-layer-copper", "role": "copper", "children": [] },
    { "kind": "pcb-layer", "id": "pcb-layer-solder-mask", "role": "solder-mask", "children": [] },
    { "kind": "pcb-layer", "id": "pcb-layer-silkscreen", "role": "silkscreen", "children": [] }
  ],
  "guides": []
}
FieldTypeNotes
version5PANEL_CONFIG_VERSION. See Versioning below.
app"zpd"Fixed literal identifying the producing app.
panel.hpnumberPanel width in Eurorack HP, from the live document (doc.panelHp).
panel.widthMmnumberDerived from hp via panelWidthMm()advisory output, not re-trusted on import (see below).
panel.heightMmnumberPANEL_HEIGHT_MM, the fixed 3U Eurorack panel height (128.5mm).
palettestring[]The three fixed color names, in index order: ["black", "gold", "white"].
layersPcbLayerContainer[]The fixed Copper, Solder mask, and Silkscreen containers in physical bottom-to-top order.
guidesGuide[]The document's ruler guides. Added in v2 (see Versioning); see Guide fields.

Note

panel.widthMm, panel.heightMm, and palette are derived/advisory output for the human or order-system reader — they exist so a person or a downstream fab tool can read the file without recomputing dimensions fromhp. On the way back in, parsePanelConfig recomputes them from hp rather than trusting hand-edited values. panel.hp and layers are the authoritative source data.

Fixed material containers

v5 persists exactly three fixed roots, in physical bottom-to-top order: Copper, Solder mask, then Silkscreen. Each has kind: "pcb-layer", a stable id, a role, optional persisted hidden, and ordinary children. The UI shows the reverse top-to-bottom order. The container determines effective material—gold/HASL copper, black solder mask, or white silkscreen—so legacy child colors are normalized on import. Solder-mask children are openings, not coverage: they mark where mask is removed. A persisted hidden: true on the Solder mask root therefore means no mask anywhere, while an empty visible root means full coverage.

The roots are not ordinary groups: users cannot rename, delete, select, group, or reorder them. Ordinary groups can be nested under each root and artwork can move across roots to change material.

Layer fields

All five layer types share a base shape plus type-specific fields.

Common to every layer

FieldTypeNotes
idstringStable layer id. Missing/empty ids receive a deterministic type/group fallback on import; collisions receive stable numeric suffixes.
namestringDisplay name. Defaults to "" if missing.
hiddenboolean (optional)Omitted while a layer has never been hidden. Once its visibility is toggled, the field is written explicitly — including false after an even number of toggles — since the toggle op flips whatever value is already there.
type'shape' | 'pattern' | 'path' | 'text' | 'image'Discriminates the fields below.

shape

FieldTypeNotes
shape'rect' | 'ellipse'
x, y, width, heightnumberMillimeters, document space.
rotationnumber (optional)Degrees clockwise around the bbox center. Omitted when unset.
color0 | 1 | 2See ColorIndex.

pattern

FieldTypeNotes
patternTypestringFree-form identifier (e.g. "dot-grid"). Kept verbatim even if unrecognized — core has no dependency on the patterns registry that would validate it.
paramsRecord<string, number>Pattern-specific numeric parameters (e.g. pitch, radius).
x, y, sizenumberPosition and side length of the movable square, in millimeters.
color0 | 1 | 2

path

FieldTypeNotes
pointsPathPoint[]Primary subpath — the one the pen tool edits. See PathPoint.
extraSubpathsPathPoint[][] (optional)Additional closed subpaths, e.g. holes/islands produced by image tracing. Presence/absence (even an empty array) is preserved exactly for round-tripping.
closedboolean
fill0 | 1 | 2 | nullnull means no fill.
stroke0 | 1 | 2 | nullnull means no stroke.
strokeWidthnumberMillimeters.

PathPoint

FieldTypeNotes
x, ynumberAnchor point, millimeters.
hin{ x: number, y: number } (optional)Absolute bezier handle-in coordinates.
hout{ x: number, y: number } (optional)Absolute bezier handle-out coordinates.

text

FieldTypeNotes
contentstringMay contain newlines.
fontFamilystring
sizeMmnumberFont size in millimeters (canvas font px equals mm in document space).
x, ynumberBounding-box top-left, millimeters.
rotationnumber (optional)Degrees clockwise. Omitted when unset.
color0 | 1 | 2

image

FieldTypeNotes
srcstringA data URL. Design-time reference only — a raster cannot be manufactured; the final panel uses the vector layers traced from it.
x, y, width, heightnumberMillimeters.
rotationnumber (optional)Degrees clockwise around the bbox center.

Guide fields

Each entry in the guides array is a ruler guide — a reference line, not a layer (see Document state & layers → Guides).

FieldTypeNotes
idstringStable guide id. Generated and collision-repaired deterministically if missing or duplicated on import.
orientation'horizontal' | 'vertical'Required. A horizontal guide is the line y = position; a vertical guide is x = position. An entry with any other value is dropped on import (a guide with no valid axis can't plant a line).
positionnumberThe line's coordinate in millimeters. Required and finite; an entry with a missing/non-finite position is dropped rather than defaulted to 0.
hiddenboolean (optional)Omitted unless set. A hidden guide renders faintly and never snaps.

ColorIndex + palette

Every layer's compatibility color field (and a path layer's fill/stroke enablement) uses a ColorIndex: 0, 1, or 2. In v5 the owning fixed container is authoritative and normalizes every non-null paint field to its material index. The mapping is defined once in packages/core/src/palette.ts:

IndexNameMeaning
0blackSolder mask
1goldExposed copper with the product's HASL finish
2whiteSilkscreen

The palette array in the envelope (["black", "gold", "white"]) is these names in index order — it exists so a reader of the JSON doesn't need to know the index-to-name mapping by heart. The palette is fixed; it is not user-configurable per document.

Versioning

PANEL_CONFIG_VERSION is currently 5, and PanelConfig pins version: 5. v1–v4 files used free root layers (v2 added guides); import deterministically partitions their paint into the v5 fixed material stack, preserving ordinary groups where possible and normalizing material from container membership. v5 input is also repaired defensively if fixed-root metadata is missing, duplicated, or reordered. This is a versioned JSON migration, not a Gerber export format; the Gerber files come from the separate artwork-only .zip export (PCB material layers & sizing).