The store behind a build-type lab: typed parts on a grid, wires between their pads. More...
| Import Statement: | import Clayground.Lab |
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.
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.
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 |
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 |
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.
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) |