Parts, grain and banding
A part is a box: three sizes, a position, a material, a grain direction and its edge banding.
{ "id": "side", "role": "side", "shape": "box", "size": ["t", "height", "depth"], "at": ["0", "0", "0"], "repeat": 2, "mirrorX": true, "material": "body", "grain": "y", "edges": { "front": "abs2" } }at is the minimum corner in the module’s own frame: x to the right, y up,
z toward the room. rotY turns the part a quarter at a time about its own
centre, so the box stays square to the world and the footprint still follows
from at and size alone.
Fixed vocabulary, because the cut list, the drilling and the interface all key on it:
side top bottom back shelf divider front drawer_front drawer_sidedrawer_back drawer_bottom plinth worktop cornice rail filler customTwo panels of identical size and different roles are two rows of the cut list. That is deliberate: a row names one role, and a sawyer reading the sheet cannot ask which of two it meant.
repeat, mirrorX, when
Section titled “repeat, mirrorX, when”repeat: 2withmirrorX: trueis how two sides are one line. The second instance is mirrored about the module, not about itself.repeat: "shelves"takes a formula, andiandnare bound inside every expression of that part.when: "front == 'door'"decides whether the part exists at all. A rule’srequiretarget is a bare name awhencan read.
grain names the axis the fibre runs along, in the MODULE’s frame: x, y,
z or none. It is one field and it decides three things:
- which way the texture runs in the scene,
- which dimension of the blank is its length,
- whether the nesting may turn the blank a quarter on a grained board.
Get it wrong and nothing fails: the panel is the right size, it is cut, it is banded, and the oak runs across the door instead of down it. The customer sees it from the other side of the room.
The blank
Section titled “The blank”You never write the blank. It is derived from the box:
- thickness is the smallest of the three extents — a panel is a sheet good, and the dimension it does not have is the one the board was bought in;
- length is the extent the grain runs along; with
grain: "none"it is simply the larger of the two remaining; - width is the other one.
So a shelf 564 × 18 × 560 with grain: "x" is a blank 564 long and 560 wide,
18 thick, with the grain along its length.
A blank with no grain can change which dimension is its length as the cabinet changes: a back panel 702 × 564 at one width is 764 × 702 at another. That is correct and it is why the import tool compares both pairings when it reads two cut lists.
Edge banding
Section titled “Edge banding”edges names the FACES of the part in the module’s frame:
"edges": { "top": "abs2", "bottom": "abs2", "left": "abs2", "right": "abs2" }front and back mean the faces toward and away from the room; left and
right are the x faces; top and bottom the y faces.
Two of the six faces of any panel cannot be banded. They are the flat of the sheet — the faces whose normal runs along the thickness — and asking for banding there is not an error you will see. The banding is silently dropped, because there is no edge to put it on.
This is the mistake that reaches the factory. A door written like this:
"edges": { "front": "abs2", "back": "abs2", "left": "abs2", "right": "abs2" }reads as “all the way round” and is not. A door’s front and back ARE the flat of the sheet. Four bands asked for, two applied, and the door arrives with its top and bottom bare — right size, right material, right count, sixteen metres of banding missing across twelve cabinets and no number anywhere that looks wrong.
The correct four for a door are top, bottom, left, right. The validator
refuses the other spelling when you publish.
Which side of the blank
Section titled “Which side of the blank”The cut list states banding in terms of the BLANK, not the module: L1 and L2
are the two long sides, W1 and W2 the two short ones. The translation goes
through rotY and the grain axis, and it is done in one place so that the
drawing, the cut list and the banding order cannot disagree.
You do not write it. You only have to know that a door banded on all four edges comes out as four sides on the sheet, and a shelf banded on its front comes out as one long side of the blank — the length of the shelf, not its depth.
Materials
Section titled “Materials”material names a SLOT — body, front, back — and the customer chooses
what goes in it. Declare what each slot wears until they do:
"materialSlots": ["body", "front", "back"],"materialDefaults": { "body": "oak_natural", "front": "white_matte", "back": "hdf_white" }Without the defaults the catalogue reports a missing finish once per slot, on a cabinet nobody has touched. The names must be materials of the catalogue the recipe is published into — a default that means one thing in your test catalogue and another in the shop resolves in neither.
Hardware
Section titled “Hardware”"hardware": [ { "key": "hinge_110", "qty": "front == 'door' ? (height > 900 ? 3 : 2) : 0" }, { "key": "slide_500", "qty": "front == 'drawers3' ? 6 : 0", "visible": false }]qty is an expression because the answer is usually zero: two thirds of the
fronts in a kitchen are not doors, and a rule per front is how a recipe becomes
unreadable. Zero is not an error and produces no line.
A key the library has never heard of is a warning rather than a refusal — the cabinet is still buildable — but the fitting reaches the price with no figure against it, and the quote says so rather than charging nothing for it.
Where it goes
Section titled “Where it goes”qty counts; place positions. Without place a fitting is counted, priced
and drawn nowhere — right for a drawer runner, wrong for a hinge.
{ "key": "hinge_110", "qty": "front == 'door' ? 2 : 0", "place": { "on": "door", "face": "inner", "along": "y", "positions": "hingePositions(i, height, n)" } }onis a part id. A repeated part isdoor[0],door[1], and"door"means every one of them — two doors take two pairs of hinges. Name the leaf to address one. A part id that does not exist is a warning, not a refusal.facepicks the plane the fitting is fixed to. Six named faces, plusinnerandouter, which are the two flats of the panel — the faces perpendicular to its thinnest axis — resolved against the cabinet rather than the part. So one line works for the far side of a door and the near side of a left-hand gable, and a hinge cup lands inside the carcass rather than through a finished front.alongsays which way the positions run across that face. The remaining axis stays on the centreline;offsetmoves all three if you need it to.positionsis evaluated once per instance, withiandnbound — the same rulerepeatandspacingfollow, and the reasonhingePositionstakes an index instead of returning a list.
Positions are measured up the board, from the carrying part’s own low edge — not from the corner of the cabinet. Otherwise every hinge formula would have to subtract the carcass offsets, and you would need to know where the solver put the door.
What this feeds: the fittings drawn in the scene, and the drilling on the DXF’s
DRILL layer — the hole pattern itself (a Ø35 cup and two Ø8 at 22 mm for a
110° hinge) comes from the catalogue, not from your recipe.