← Back to Docs
  • index.html
  • Clayground
  • Clayground.Lab
  • Board
  • Clayground 2026.7
  • Board QML Type

    The store behind a build-type lab: typed parts on a grid, wires between their pads. More...

    Import Statement: import Clayground.Lab

    Properties

    Signals

    Methods

    Detailed Description

    Everything a lab that places parts does with them that has no domain in it - the cell arithmetic, where a pad is once the part is turned, the hit test, keep-out and the search for a free cell, adding, moving, turning and removing, tapping a wire to branch from it, batching the mutations so a preset that makes eighty of them publishes once, and the serialization that survives a reload. The domain hands over a spec describing its part types (see board.js) and reads the parts back through changed to solve them; the board never interprets a part's fields.

    Mutations publish once

    Every mutation edits parts / wires in place and then says so. Outside a batch that publishes immediately - a fresh array so QML sees a change, a bumped rev, and changed - which is what one click wants. Inside beginBatch / endBatch it only marks what changed: reassigning the array hands a Repeater3D a new model and it rebuilds every part (22 ms at 38 parts, measured), so a preset making 85 mutations would rebuild roughly 740 parts to end up with 38. Every bulk edit path must go through a batch or it quietly reintroduces that cost.

    Board {
        id: board
        cols: 28; rows: 16; cell: 5
        spec: Parts.spec
        router: ({ all: Route.routeAll, one: (a, b, lane) => Route.routeOne(a, b, [], null, lane) })
        onChanged: (kind) => { if (kind !== "view") root.resolve() }
    }

    See also BoardInput, BoardWires3D, PartCard, BoardOverlay, BoardPalette, and PartPlacer.

    Property Documentation

    cell : real


    cell \brief World units per cell. : real


    cols : int


    cols \brief Cells across. : int


    cursorHeight : real


    cursorHeight \brief The y \l cursorW is reported at. : real


    cursorW : var


    cursorW \brief The cursor on the board, in world units. : var


    eraser : bool


    eraser \brief The eraser tool is on. : bool


    hoverHit : var


    hoverHit \brief What is under the cursor, as \l hitAt reports it. : var


    lane : real


    lane \brief The raster a router turns on; half a cell by default. : real


    nextId : int


    nextId \brief The next id handed out, shared by parts and wires. : int


    padY : real

    A pad in world space, turned with its part; y is padY.


    parts : var

    [{ id, type, col, row, rot, ...fields }] - col/row are fractional cells.

    Mutated in place by the board's own verbs; read it, never assign it. A binding that reads a part must list rev as well: a mutation hands back the same object, and re-assigning an identical reference is not a change as far as QML is concerned.


    rev : int

    Bumped by every mutation and every move; the dependency in-place edits need.


    router : var

    Optional: { all(links, obstacles, lane), one(a, b, lane) } choosing wire paths.

    Without one a wire is the straight line between its pads. The circuit kit's route.js draws Manhattan paths; it stays in the kit, the board only asks it. all is handed every wire at once (a wire has to know which lanes are taken) and returns { wireId: [{x, z}, ...] }; one routes the dangling preview.


    routes : var [read-only]

    Every wire's drawn path, { wireId: [{x, z}, ...] }, from the router.

    Routing is GEOMETRY: it follows rev and never the solve, so a search over candidate paths cannot end up inside the solve loop.


    rows : int


    rows \brief Cells deep. : int


    selectedId : int


    selectedId \brief The selected part, -1 for none. : int


    spec : var

    The domain's part types: { type: { terminals, half, actuator, keepOut, fields, rows, watch } }.


    wires : var


    wires \brief \c {[{ id, a: [partId, ti], b: [partId, ti] }]}. : var


    wiringFrom : var


    wiringFrom \brief \c {{el, ti}} while a wire is dangling. : var


    Signal Documentation

    changed(string kind)

    The board was mutated: "solve" after anything the domain must re-solve, "view" after a move or a turn.

    Note: The corresponding handler is onChanged.


    cleared()

    Note: The corresponding handler is onCleared.


    removed(int id)

    Note: The corresponding handler is onRemoved.


    Method Documentation

    var actuatorHalf(string type)


    int addJunction(real col, real row)

    A solder dot, placed exactly (never snapped): wires meet here.


    int addPart(string type, real col, real row)

    Places a part on the nearest free cell; returns its id, -1 when the board is full.


    int addRotated(string type, real col, real row, int quarters)

    Place and turn in one go - a quarter turn is 90 degrees counter-clockwise seen from above.


    void addWire(var a, var b)


    void beginBatch()


    var bodyHalf(string type)


    var bounds(var which, real pad)

    Corner points framing these parts (a list of parts, of ids, or nothing for all), padded.


    bool cellFree(real col, real row, int ignoreId, string type)


    real cellX(real col)


    real cellZ(real row)


    void clear()


    var closestOnPath(var path, real x, real z)


    real colOf(real x)


    void endBatch()


    var hitAt(real wx, real wz)

    What is at a board point: an actuator, a terminal, an element or a wire - in that order - or null.


    var jumpTargets(var labelOf, var groupOf, real y)

    HintJump targets, one per part, labelled by the domain.


    int keepOut(string type)


    var keys(var grid, var overlay)

    The board's half of the key map, as LabKeys entries: C clear, E eraser, V values, Q plot, R turn, # grid, Del remove.


    void load(var s)


    void movePart(int id, real col, real row, bool snap)

    Moves a part; snapping lands on a free peg cell, else it follows freely.


    var nearestFreeCell(real col, real row, string type)


    var partAt(int id)


    var previewPath(var from, var to)

    The path a wire from pad from ({el, ti}) to the point to ({x, z}) would take.


    void removePart(int id)


    void removeWire(int id)


    void rotatePart(int id)


    real rowOf(real z)


    var selectionKeys(var monitor, var grid)

    The keys that act on the selection: Q plot, R turn, Del remove - and # / G for the grid.


    bool setField(int id, string key, var value)

    Sets one domain field and publishes; false when nothing changed.


    var specOf(string type)


    int splitWireAt(int wireId, real wx, real wz)

    Drops a junction onto a wire where it was clicked and splits it in two; returns the junction id.


    var state()


    int terminalCount(string type)


    var terminalDir(int id, int ti)


    var terminalLocal(string type, int ti)


    var wireMid(var wire)


    var wirePath(var wire)