← Skills

Wood Boards

Native Read-only

Board stock dimensions, grain direction, sheet goods, and standard lumber sizing for woodworking projects.

/skills/wood_boards.md

Estimated tokens
2257
Characters
9026
Source
Native

Markdown

# Wood Boards Skill

Pick the builder from `elixifree.domains.woodworking` by the part's shape and orientation:

| Part | Builder |
|------|---------|
| Horizontal rectangular parts — tops, aprons, rails, stretchers, shelves, panels | `Board` |
| **Vertical rectangular parts** — legs, posts, columns (declared bounding box has Z as its largest dimension) | `Leg` |
| Cylindrical parts — round legs, turned spindles, dowel pins, discs | `Dowel` |

Do NOT use `Part.makeBox`, `Part.makeCylinder`, or any raw FreeCAD API. Do NOT write `# ElixiFree gap:` unless a modifier listed below genuinely cannot express the shape.

**Orientation rule (non-negotiable):** `Board` builds its length along the **X axis** and cannot stand a part upright. If the component's declared bounding box is Z-dominant (height is the largest dimension), you MUST use `Leg` (rectangular) or `Dowel` (cylindrical) — both build along Z. A leg built with `Board` comes out lying on its side and fails anchor verification.

## Script template — use this for every board component

```python
from elixifree.domains.woodworking import Board

# === PARAMETERS ===
LENGTH = 1200    # mm — along grain direction
WIDTH = 600      # mm
THICKNESS = 25   # mm

result = Board(length=LENGTH, width=WIDTH, thickness=THICKNESS, material="oak", grain="face").build()
result.add_to_doc("Body")
```

That is the complete script. Do not add `import FreeCAD`, `doc = ...`, `doc.recompute()`, or any other lines.

## Parameters

| Param | Values | When to use |
|-------|--------|-------------|
| `material` | `"oak"`, `"walnut"`, `"pine"`, `"birch_ply"`, `"mdf"` | Match project material |
| `grain` | `"long"` | Legs, rails, aprons, stretchers |
| `grain` | `"face"` | Tops, shelves, sheet goods |

## Optional modifiers (chain before `.build()`)

**Tapered horizontal part** (taper along the X length axis — for tapered legs use `Leg`, see Vertical Parts below):
```python
result = Board(length=700, width=40, thickness=40, material="oak", grain="long").taper(40, 25, 40, 25).build()
result.add_to_doc("Body")
```

**Chamfered edges:**
```python
result = Board(length=800, width=100, thickness=38, material="oak", grain="long").chamfer("all_long", 3).build()
result.add_to_doc("Body")
```

**Decorative arc cut on apron:**
```python
result = Board(length=500, width=80, thickness=22, material="oak", grain="long").arc_cut("bottom", 50, "concave").build()
result.add_to_doc("Body")
```

## Joinery modifiers — always use these, never raw Part booleans

Aprons, rails, and stretchers commonly need mortises or tenons. Add them as modifiers — do NOT fall back to `# ElixiFree gap:` for joinery. (Mortised **legs** use `Leg` — see Vertical Parts below.)

**Apron with tenon at one end:**
```python
result = (
    Board(length=400, width=80, thickness=22, material="oak", grain="long")
    .tenon("end", 14, 0, 12, 40, 32)  # x, z, width, height, length
    .build()
)
result.add_to_doc("Body")
```

**Apron with tenon at each end** (joins two legs — the standard apron pattern):
```python
result = (
    Board(length=400, width=80, thickness=22, material="oak", grain="long")
    .tenon("end", 14, 0, 12, 40, 32)    # tenon protruding past X=length
    .tenon("start", 14, 0, 12, 40, 32)  # tenon protruding past X=0
    .build()
)
result.add_to_doc("Body")
```

Valid face names for `.tenon()`:
| face | protrudes past | use when |
|------|---------------|----------|
| `"end"` or `"right"` | X=length face | right end of apron/rail |
| `"start"` or `"left"` | X=0 face | left end of apron/rail |
| `"front"` | Y=0 face | tenon on front face |
| `"back"` | Y=width face | tenon on back face |

Modifier signature:
- `.mortise(face, x, z, width, height, depth)` — cuts a pocket into the board
- `.tenon(face, x, z, width, height, length)` — adds a protruding peg

Valid face names for `.mortise()`:
| face | opens onto | use when |
|------|-----------|----------|
| `"end"` or `"front"` | Y=0 face | apron joins from front |
| `"back"` | Y=width face | apron joins from back |
| `"side"` or `"right"` | X=length face | rail joins from right end |
| `"left"` | X=0 face | rail joins from left end |
| `"top"` | Z=thickness face | through-mortise from top |
| `"bottom"` | Z=0 face | pocket receives a tenon from **below** |

**Show-face rule (non-negotiable):** pockets that receive legs or tenons from
below — e.g. a table top receiving leg tenons — MUST use `"bottom"`. Never cut
them with `"top"`: the part is placed unrotated, so a `"top"` pocket ends up
visible on the finished surface. Use `"top"` only for genuine through-mortises
or pockets meant to be seen from above.

**Table top with four leg-tenon pockets** (pockets open downward, show face stays clean):
```python
result = (
    Board(length=1100, width=550, thickness=25, material="walnut", grain="face")
    .mortise("bottom", 53, 53, 14, 14, 14)     # front-left  (x, y, width, height, depth)
    .mortise("bottom", 1033, 53, 14, 14, 14)   # front-right
    .mortise("bottom", 53, 483, 14, 14, 14)    # back-left
    .mortise("bottom", 1033, 483, 14, 14, 14)  # back-right
    .build()
)
result.add_to_doc("Body")
```
For `"top"`/`"bottom"` the second and third arguments are the pocket's X and Y
positions on the face.

Catalog proportions for hardwood medium stock (25–50mm thickness): width=12, height=40, depth/length=32.

**NEVER write `# ElixiFree gap:` for joinery.** Wanting "explicit control", "precision", or "boolean operations" is not a valid gap reason — the modifiers above are the explicit, precise boolean operations. Only gap if the shape is physically impossible with the Board/Leg modifiers (curved board, etc.). Cylindrical cross-sections are NOT a gap — use `Dowel` instead (see below). Vertical rectangular parts are NOT a gap — use `Leg` (see below).

## Vertical Parts — use Leg, never Board

Any **rectangular leg, post, or column** — a part whose declared bounding box has Z (height) as its largest dimension — MUST use `Leg`. It builds standing along Z: the bounding box is `X=top_side, Y=top_side, Z=height` (+ tenon length if present), foot centered under the top.

All Leg coordinates are world-frame and natural: mortise faces are the four vertical faces, and z positions are measured **up from the floor**.

**Straight post:**
```python
from elixifree.domains.woodworking import Leg

result = Leg(height=700, top_side=40, material="oak").build()
result.add_to_doc("Body")
```

**Tapered leg with top tenon and two mortises** (40×40mm top tapering to 25×25mm foot, 375mm tall, 13mm cube tenon on top, mortises on the two inner faces 50mm up from the floor):
```python
from elixifree.domains.woodworking import Leg

HEIGHT     = 375   # mm — leg height (Z)
TOP_SIDE   = 40    # mm — cross-section at top
FOOT_SIDE  = 25    # mm — cross-section at foot

result = (
    Leg(height=HEIGHT, top_side=TOP_SIDE, foot_side=FOOT_SIDE, material="walnut")
    .tenon_top(13, 13, 13)                # width (X), depth (Y), length (protrusion above top)
    .mortise("right", 50, 13, 40, 14)     # face, z_from_floor, width, height, depth
    .mortise("back", 50, 13, 40, 14)
    .build()
)
result.add_to_doc("Body")
```

Leg modifier signatures:
- `.tenon_top(width, depth, length)` — tenon centered on the top face, protruding above Z=height
- `.mortise(face, z_bottom, width, height, depth, offset=0.0)` — pocket on a vertical face; `z_bottom` is measured up from the floor; centered horizontally unless `offset` shifts it

Valid face names for `Leg.mortise()`:
| face | opens onto |
|------|-----------|
| `"front"` | Y=0 face |
| `"back"` | Y=top_side face |
| `"left"` | X=0 face |
| `"right"` | X=top_side face |

## Cylindrical Parts — use Dowel, never Board

Any part described as cylindrical, round, or turned MUST use `Dowel`, not `Board`. This includes:
- Round/cylindrical **legs** and spindles
- Round **seat discs** and circular tops
- Rungs, dowel pins, and any other circular cross-section part

`Dowel(diameter, length, material)` — the bounding box is `X=diameter, Y=diameter, Z=length`.

**Cylindrical leg** (40mm diameter × 475mm tall):
```python
from elixifree.domains.woodworking import Dowel

DIAMETER = 40   # mm
LENGTH = 475    # mm

result = Dowel(diameter=DIAMETER, length=LENGTH, material="oak").build()
result.add_to_doc("Body")
```

**Round seat disc** (400mm diameter × 35mm thick) — `length` = the thickness:
```python
from elixifree.domains.woodworking import Dowel

DIAMETER = 400  # mm
THICKNESS = 35  # mm — this is the "length" parameter (Z height)

result = Dowel(diameter=DIAMETER, length=THICKNESS, material="oak").build()
result.add_to_doc("Body")
```

**Bounding box rule for Dowel:** the bounding box is always `X=diameter, Y=diameter, Z=length`. For a seat disc where the goal says "400mm diameter × 35mm thick", use `diameter=400, length=35`.

**Through-mortises and holes** on round parts (e.g. leg holes in a seat disc) are cut at the constructability stage — do NOT attempt to cut them in the Dowel script. Generate the pure disc shape only.
v0.0.985