Skip to content

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_side
drawer_back drawer_bottom plinth worktop cornice rail filler custom

Two 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: 2 with mirrorX: true is how two sides are one line. The second instance is mirrored about the module, not about itself.
  • repeat: "shelves" takes a formula, and i and n are bound inside every expression of that part.
  • when: "front == 'door'" decides whether the part exists at all. A rule’s require target is a bare name a when can 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:

  1. which way the texture runs in the scene,
  2. which dimension of the blank is its length,
  3. 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.

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.

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.

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.

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": [
{ "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.

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)" } }
  • on is a part id. A repeated part is door[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.
  • face picks the plane the fitting is fixed to. Six named faces, plus inner and outer, 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.
  • along says which way the positions run across that face. The remaining axis stays on the centreline; offset moves all three if you need it to.
  • positions is evaluated once per instance, with i and n bound — the same rule repeat and spacing follow, and the reason hingePositions takes 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.