Repository navigation
Save File Format
Export board writes the layout to control-function-layout.json.
Import board reads one back, replacing the canvas as a single undoable
step. Files are validated on the way in — anything that isn't a Control
Function layout is refused and leaves your work untouched.
{
"format": "cf.layout",
"version": 3,
"unit": "u",
"groups": [
{ "id": "g1", "x": 4, "y": 2, "rotation": 15, "pivot": { "x": 0, "y": 0 } }
],
"pcbs": [
{ "id": "p1", "shape": "rectangular", "offset": 1, "name": "Left half",
"controller": "Elite-C", "hotswap": true, "diodeDefault": true, "backlight": false,
"stabilizerStyle": "screw-in", "usb": { "t": 0.875, "connector": "usb-c" },
"batteryPresent": false, "batteryCapacity": "" }
],
"links": [
{ "id": "l1", "boardIds": ["p1", "p2"], "connector": "trrs" }
],
"rows": [{ "id": "r1", "name": "Row 01" }],
"cols": [{ "id": "c1", "name": "Column 01" }],
"keys": [
{ "x": 0, "y": 0, "w": 1, "h": 1, "shape": "rectangular",
"primary": "2", "secondary": "@", "tertiary": "€", "fn": "", "home": false,
"groupId": "", "pcbId": "p1", "rowId": "r1", "colId": "c1" }
]
}A key's groupId, if set, names an entry in groups, and its x/y are
then relative to that group's own origin. Its pcbId, rowId and colId
name entries in pcbs, rows and cols the same way — that is the whole of
a board's, row's or column's membership. All four ids are remapped on import.
A board carries no geometry. shape is rectangular or cutout;
offset is a whole number of 0.25u steps, 0–8, defaulting to 1. name
is free text and unique — a duplicate keeps the first board and blanks the
rest. controller and stabilizerStyle are free text, with an unrecognized
value falling back to unset. hotswap and backlight default to false;
diodeDefault defaults to true, so an older save keeps the original BOM
assumption. usb is { t, connector } — a 0–1 fraction of the perimeter and
usb-c/micro-usb — or null for no marker; the old { edge, t } form is
converted rather than dropped. batteryPresent and batteryCapacity exist on
every board regardless of linking.
A link's boardIds names entries in pcbs, remapped on import; a link left
with fewer than two resolving boards is dropped. connector is trrs, trs
or wireless, defaulting to trrs.
A rows/cols entry is nothing but { id, name }; its members are exactly
the keys naming it. One with no members after import is removed.
Older files still load. Version 2 and 1 predate rows and columns, so those
default to unset. Version 1 also describes a board as a placed x/y/w/h
rectangle with no pcbId on any key: on import that rectangle runs its old
"is this key's centre inside" test one last time, the answer is written down
as membership, and the rectangle is dropped. Saves from before cutout
existed read back as rectangular with no offset.