# Edge & Face Cuts

*Dado, groove, rabbet, chamfer, roundover, moulding, taper, hole, and trim*

**Names:** `dado` `groove` `rabbet` `chamfer` `roundover` `moulding` `taper` `hole` `trim` `edges` `faces` `face` `offset` `stopped` `start` `run_length` `stop` `size` `width` `depth` `target_dim` `start_z` `end_z` `target` `reference` `at` `length` `diameter` `ovolo` `cove` `ogee` `thumbnail` `half_round` `cockbead` `ramp` `lambs_tongue` `top_perimeter` `bottom_perimeter` `front_perimeter`

## When to use

Material removal on one part, written inside its `component` (or an inline `part`) in the
part's own frame. Every feature on a part lowers to a single cut: the compiler unions every
cutter and cuts once, so **write order does not matter**. Use this file for housings, panel
grooves, edge profiles, tapered legs, holes and slots, and levelling a raked leg to the floor.
For an outline cut through the thickness use `cut-paths`; for joints between two parts use
`joinery-contract`. A veneer face is `veneer-and-laminate`.

## Syntax

Enum lists (`edges`, `faces`) are **child nodes** taking one or more arguments; KDL v2 has no
array literals.

| Node | Required | Notes |
| --- | --- | --- |
| `dado` | `width` `depth` `offset` | Runs **across** the grain. |
| `groove` | `width` `depth` `offset` | Runs **with** the grain. A face the grain runs into (end grain) skips the cut at that face. |
| `rabbet` | `width` `depth` + `{ edges ... }` or `face=` `edge=` | `face=`/`edge=` reaches any edge, vertical ones included. Stoppable, square-ended only. [`bevel-and-rabbet`](bevel-and-rabbet.md). |
| `bevel` | `angle` + `{ edges ... }` or `face=` `edge=` | Built (RFC-0143). [`bevel-and-rabbet`](bevel-and-rabbet.md). |
| `chamfer` | `size` + `{ edges ... }` | Stoppable (below). |
| `roundover` | `size` + `{ edges ... }` | Stoppable; `stop=lambs_tongue` refused. |
| `moulding` | `section=` `size=` + `{ edges ... }` | Stoppable (`stopped=` `start=` `run_length=` `stop=square\|ramp`). `section=` one of `ovolo cove ogee thumbnail half_round cockbead`; any other word is refused. |
| `taper` | `start_z` `end_z` `target_dim` + `{ faces ... }` | Full section at `start_z`, `target_dim` at `end_z`, on the named faces. |
| `hole` | `diameter` `depth` | `depth=0.0` is through. `x=` `y=` `z=` and `normal=` (the face it is drilled into) place it. `length=` + `axis=(x\|y)` make a slot. |
| `trim` | `reference=world\|assembly` `normal=` `at=` + `{ faces ... }` | The one feature resolved in **world** or assembly space, after placement. |

- **Edge selectors** (`edges` children): `top_perimeter`, `bottom_perimeter`,
  `front_perimeter`, `all`; named edges `top_n top_s top_e top_w` and `bottom_n bottom_s
  bottom_e bottom_w`; the four vertical arrises `upright_ne upright_nw upright_se
  upright_sw`; the four edges of the front face `front_top front_bottom front_e front_w`.
- **Face words** (`face=`, `faces`): `pos_x neg_x pos_y neg_y pos_z neg_z`, part-local.
- **Placing a `dado`/`groove` (ADR-0011).** `face=` is the face it is cut into (default: the
  broad face; **required** on a square section). `offset=` is the distance of the cut's *low
  edge* from the low end of its axis (the bottom face for z): always positive, "up from the
  bottom" or "in from the front or left". `stopped=#true` with `start=` (default 10 mm in) and
  `run_length=` (default: out through the far end) stops it. Which way it runs is not a
  property: it comes from the grain. The removed names `offset_bottom` and `offset_x` do not
  exist; the build reports them by name.
- **`target=<Name>`** on a `dado`/`groove` (a bare word, e.g. `target=panel`) names the part the
  cut captures. It is documentation for the reader and the joint rules (a `stile_and_rail`
  frame's grooves, `joinery-frames`), not geometry: it moves nothing.
- **`id=`/`label=`** on a `dado`/`groove` are accepted syntax (both parse as strings) but
  nothing in the compiler reads them back: no diagnostic, no cross-reference, no cut-list or
  drawing output resolves against them (issue #549). They are for the author's own
  bookkeeping or future-proofing only — a name you can grep your own source for — not yet a
  real reference mechanism another node can resolve against.
- **Stopped `chamfer`/`roundover` (RFC-0025):** `stopped=#true start= run_length=
  stop=square|ramp` (`square` default). `chamfer` alone also takes `stop=lambs_tongue`.
  A chamfer or roundover on a vertical arris is refused if the run overlaps a `taper` on
  either of its faces.
- **`moulding size=`** is the profile's overall projection, measured as `chamfer`/`roundover`'s
  `size`. Every section is a stepped polygon (16 chords per quarter circle). `half_round` and
  `cockbead` are *applied* mouldings: author them as their own narrow `component` (a strip
  whose stock is the bead's section, profile cut on the faces that show) and `join` it on; a
  cut on the host can never leave material standing proud.
- **Most profiles do not follow a `cut_path` outline:** `rabbet`/`bevel`/`moulding` and any
  edge selector naming individual compass edges ease the blank's straight edges only. On a
  part with a through `cut_path` that changes the outline, a perimeter selector
  (`front_perimeter`, `all`) leaves the sawn edges plain and reports "shaped only along the
  blank's own edges" in the skips. A named edge (`top_n`) the saw has taken away entirely is
  skipped as "sawn away" (a profile there would cut air); a named edge the saw only shortened
  is still shaped along what is left. A true profile along a curved run is not built
  (RFC-0126 addendum).
- **`chamfer`'s and `roundover`'s own `top_perimeter`/`bottom_perimeter` are the exception**
  (RFC-0021, issue #58 Part A): on a part whose outline came from a through,
  `keep="inside"` `cut_path`, these two sweep the real outline instead — arcs and
  spline-flattened runs included — rather than the box's four compass edges, which such a
  part does not have at all (`three_legged_stool.kdl`'s rounded-triangle `Seat`).
  `stopped=` is refused there (no single straight edge's own length to measure a run
  against).
- **How big a profile may be:** a `chamfer`, `roundover`, `moulding` or `rabbet` is cut into
  the corner of two faces and must stop **strictly short** of the opposite face of each,
  otherwise it is skipped ("would remove the whole section", naming the short dimension).
  So `size` is *not* "the strip's thickness": a half-round strip needs thickness **greater
  than** `size` (and width greater than `size`); `size = width / 2` with both long edges
  moulded is the true semicircle. A strip 6 thick takes `size` up to just under 6, not 6.
- **Stop a rabbet (or any stopped cut) at a height**: there is no "stop at extent" keyword.
  Measure on the edge's run axis from its **low end**: `stopped=#true start=0.0
  run_length=<height>` stops it `<height>` up; for a run measured down from the high end,
  `start=<length - height>` and leave `run_length` out. Omitting `start=` begins 10mm in.
- **`hole`** with `length=`/`axis=` (RFC-0030) is a slot: `length` is the straight run between
  the two semicircular caps and `axis` (`x` or `y`) the part-local axis it runs along. Omit
  `length` (or `0.0`) for a round hole; `axis=` does nothing then, so a `repeat` can pass both
  and ternary only `length`.
- **`id=`/`label=`** on a `hole` are accepted syntax (both parse as strings) but nothing in
  the compiler reads them back: no diagnostic, no cross-reference, no cut-list or drawing
  output resolves against them (issue #551, same gap as `dado`/`groove`, issue #549). They
  are for the author's own bookkeeping only — a name you can grep your own source for — not
  yet a real reference mechanism another node can resolve against.
- **`trim` (RFC-0024; `reference=assembly`, #458)**: `normal=` is a direction in
  `reference=`'s own frame naming the side of the plane that is **kept**; `at=` is where the
  plane sits along that axis (a CEL expression, usually derived, `"0.0 - leg_drop"`). `faces`
  names the part's **own** face the cut is written from. Pick a *side* face nearly
  perpendicular to the plane, not the end the trim conceptually shortens: a face leaning past
  60 degrees (`MAX_LEAN`) is refused. `reference=` is `world` (the document's own origin) or
  `assembly` (the nearest enclosing `instance=`-placed assembly's own frame; identity, same
  as `world`, when the part has no such ancestor).

## Example

```kdl fragment=component
// housing across the grain, a panel groove in an edge, a stopped groove
dado face=pos_y width=19.0 depth=8.0 offset=40.0
groove face=pos_y width=6.0 depth=10.0 offset="(100.0 - 6.0) / 2.0"
groove face=pos_y width=10.0 depth=6.0 offset=40.0 stopped=#true start=50.0 run_length=400.0
// edge treatments
chamfer size=3.0 {
    edges top_perimeter
}
roundover size=4.0 stopped=#true start=60.0 run_length=400.0 stop=ramp {
    edges upright_se upright_sw
}
moulding section=ogee size=4.0 {
    edges bottom_perimeter
}
rabbet width=10.0 depth=6.0 {
    edges front_top
}
```

```kdl fragment=component
taper start_z=-200.0 end_z=-600.0 target_dim=60.0 {
    faces pos_x pos_y
}
// a row of holes where only the outer two are slots
repeat i count=4 {
    hole id="Peg_{i}" diameter=10.0 depth=0.0 x="20.0 + (i * 20.0)" y=40.0 z=0.0 length="(i == 0.0 || i == 3.0) ? 16.0 : 0.0" axis=x
}
```

## Pitfalls and diagnostics

- **Not built features are skipped, never silently ignored.** `lamination`, `pocket`,
  `spline` and the legacy component-level `mortise`, `tenon`, `dovetail`, `box_joint` (etc.)
  appear as a `skipped` entry in the scene JSON and as `ww check`'s warning
  "`lamination` not built: ...". Nothing is cut.
- `bevel`, `rabbet face= edge=` and stopped `moulding` are built; see
  [`bevel-and-rabbet`](bevel-and-rabbet.md). `trim reference=assembly` is built (#458); only
  a `reference=` naming neither `world` nor `assembly` is refused.
- `stop=lambs_tongue` on a `roundover` is refused, named. A `section=` outside the six words is
  refused with the reason.
- A feature that never reaches the part (a `trim` whose face never crosses the plane) is a
  skip in the build report, not a silent no-op.
- Padding stock only to give `trim` something to cut is real waste
  (`ww::lower::wasteful_stock`). Size the stock so the **shallowest** corner lands on the plane.
- A `groove` or `dado` cut into a face the grain runs into (end grain) is skipped.
- `offset` is always positive and measured from the low end; do not use negative z here.
- Features in a `repeat` body need `id="Name_{i}"` wherever an id or mark would collide.

## See also

- `docs/KDL_AUTHORING.md` §7; ADR-0011 (dado/groove placement); RFC-0024 (`trim`), RFC-0025
  (stopped profiles), RFC-0030 (slots), RFC-0126 (mouldings).
- `examples/three_legged_stool.kdl` (`trim`), `examples/desk.kdl` (dados), `examples/dining_table.kdl`.
- `cut-paths.md`, `veneer-and-laminate.md`, `joinery-contract.md`, `components-and-stock.md`.

## Names

Every name this page declares, how the example library uses it, and the shortest real use.

### `dado`

feature; node in 23 of 116 examples (63 uses).

Shortest real use, from `tall_white_cutout_cabinet.kdl`:

```kdl
dado face=pos_x width=shelf_th depth=dd offset=plinth
```

### `groove`

feature; node in 40 of 116 examples (145 uses).

Shortest real use, from `kumiko_andon_lantern.kdl`:

```kdl
groove face=pos_z width=gw depth=gd offset="(post_s - gw) / 2.0"
```

### `rabbet`

feature; node in 24 of 116 examples (56 uses).

Shortest real use, from `tall_white_cutout_cabinet.kdl`:

```kdl
rabbet face=pos_x edge=pos_y width=back_th depth=rd
```

### `chamfer`

feature; node in 30 of 116 examples (56 uses).

Shortest real use, from `adirondack_plank_chair.kdl`:

```kdl
chamfer size=3.0 { edges top_perimeter }
```

### `roundover`

feature; node in 42 of 116 examples (70 uses).

Shortest real use, from `adirondack_plank_chair.kdl`:

```kdl
roundover size=3.0 { edges top_perimeter }
```

### `moulding`

feature; node in 18 of 116 examples (36 uses); value in 2 of 116 examples (4 uses).

Shortest real use, from `farmhouse_harvest_table.kdl`:

```kdl
moulding section=thumbnail size=6.0 {
    edges top_s
}
```

### `taper`

feature; node in 12 of 116 examples (27 uses); property in 4 of 116 examples (4 uses).

Shortest real use, from `deco_side_chair.kdl`:

```kdl
taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}
```

### `hole`

feature; node in 23 of 116 examples (29 uses).

Shortest real use, from `pennsylvania_schrank.kdl`:

```kdl
hole
```

### `trim`

feature; node in 5 of 116 examples (7 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from `mid_century_credenza.kdl`:

```kdl
trim reference=world normal=pos_z at=floor_z {
    faces pos_x
}
```

### `edges`

word-valued property; node in 71 of 116 examples (189 uses).

Shortest real use, from `dining_chair.kdl`:

```kdl
edges top_n
```

### `faces`

word-valued property; node in 15 of 116 examples (34 uses).

Shortest real use, from `deco_side_chair.kdl`:

```kdl
faces neg_y
```

### `face`

property; node in 1 of 116 examples (1 uses); property in 77 of 116 examples (483 uses).

Shortest real use, from `blanket_chest.kdl`:

```kdl
face "'neg_y'"
```

### `offset`

property; property in 57 of 116 examples (240 uses).

Shortest real use, from `deco_console_table.kdl`:

```kdl
stringing face=neg_y material=Ivory offset=8.0
```

### `stopped`

property; node in 7 of 116 examples (7 uses); property in 40 of 116 examples (135 uses).

Shortest real use, from `biedermeier_chest_of_drawers.kdl`:

```kdl
stopped #true
```

### `start`

property; property in 40 of 116 examples (136 uses).

Shortest real use, from `blanket_chest.kdl`:

```kdl
avoid start="carcass_height - till_lid_th - till_height" width=till_stock_th
```

### `run_length`

property; property in 36 of 116 examples (123 uses).

Shortest real use, from `tansu_kiri_chest.kdl`:

```kdl
groove face=neg_z width=door_groove_w depth=8.0 offset=track_y stopped=#true start=0.0 run_length=left_w
```

### `stop`

word-valued property; property in 5 of 116 examples (15 uses).

Shortest real use, from `ebonised_incised_cabinet.kdl`:

```kdl
chamfer size=chamfer_sz stopped=#true start=14.0 run_length="post_h - 28.0" stop=lambs_tongue {
    edges upright_sw
}
```

### `size`

property; node in 1 of 116 examples (3 uses); property in 73 of 116 examples (198 uses).

Shortest real use, from `bookcase.kdl`:

```kdl
size domino_size
```

### `width`

property; node in 46 of 116 examples (66 uses); property in 56 of 116 examples (292 uses); value in 22 of 116 examples (38 uses).

Shortest real use, from `appalachian_post_and_rung_bench.kdl`:

```kdl
width 20.0
```

### `depth`

property; node in 59 of 116 examples (122 uses); property in 95 of 116 examples (571 uses); value in 33 of 116 examples (74 uses).

Shortest real use, from `glazed_vitrine_whiplash_crest.kdl`:

```kdl
depth 8.0
```

### `target_dim`

property; property in 12 of 116 examples (27 uses).

Shortest real use, from `deco_side_chair.kdl`:

```kdl
taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}
```

### `start_z`

property; property in 12 of 116 examples (27 uses).

Shortest real use, from `deco_side_chair.kdl`:

```kdl
taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}
```

### `end_z`

property; property in 12 of 116 examples (27 uses).

Shortest real use, from `deco_side_chair.kdl`:

```kdl
taper start_z=0.0 end_z=seat_z target_dim=leg_dim {
    faces pos_y
}
```

### `target`

word-valued property; property in 33 of 116 examples (95 uses).

Shortest real use, from `ebonised_incised_cabinet.kdl`:

```kdl
groove target=panel face=neg_z width=panel_th depth=groove_d offset=panel_off
```

### `reference`

word-valued property; property in 5 of 116 examples (7 uses); value in 3 of 116 examples (3 uses).

Shortest real use, from `kitchen_collections.kdl`:

```kdl
part Fridge role=reference label="Refrigerator" {
    stock Appliance (in)36 (in)30 (in)68
    by top_sw
    pos x=(in)42 y=0.0 z=0.0
}
```

### `at`

property; node in 47 of 116 examples (1024 uses); property in 10 of 116 examples (18 uses).

Shortest real use, from `windsor_round_stool.kdl`:

```kdl
at z=-4.0 { circle r=rf }
```

### `length`

property; node in 59 of 116 examples (154 uses); property in 50 of 116 examples (234 uses); value in 46 of 116 examples (203 uses).

Shortest real use, from `appalachian_ladder_back_side_chair.kdl`:

```kdl
length 26.0
```

### `diameter`

property; node in 50 of 116 examples (94 uses); property in 65 of 116 examples (129 uses).

Shortest real use, from `appalachian_post_and_rung_bench.kdl`:

```kdl
diameter 9.0
```

### `ovolo`

value in 2 of 116 examples (5 uses).

Shortest real use, from `jacobean_joined_chest.kdl`:

```kdl
moulding section=ovolo size=rail_ovolo {
    edges front_top
}
```

### `cove`

value in 3 of 116 examples (3 uses).

Shortest real use, from `pierced_splat_side_chair.kdl`:

```kdl
moulding section=cove size=5.0 {
    edges upright_sw upright_nw upright_se
}
```

### `ogee`

value in 9 of 116 examples (14 uses).

Shortest real use, from `blanket_chest.kdl`:

```kdl
moulding section=ogee size=4.0 {
    edges top_perimeter
}
```

### `thumbnail`

value in 6 of 116 examples (9 uses).

Shortest real use, from `farmhouse_harvest_table.kdl`:

```kdl
moulding section=thumbnail size=6.0 {
    edges top_s
}
```

### `half_round`

value in 2 of 116 examples (4 uses).

Shortest real use, from `william_and_mary_chest_of_drawers.kdl`:

```kdl
moulding section=half_round size=bead_size {
    edges front_e front_w
}
```

### `cockbead`

value in 1 of 116 examples (1 uses).

Shortest real use, from `federal_chest_of_drawers.kdl`:

```kdl
moulding section=cockbead size=3.0 {
    edges front_perimeter
}
```

### `ramp`

not used by any example yet.

### `lambs_tongue`

value in 5 of 116 examples (15 uses).

Shortest real use, from `ebonised_incised_cabinet.kdl`:

```kdl
chamfer size=chamfer_sz stopped=#true start=14.0 run_length="post_h - 28.0" stop=lambs_tongue {
    edges upright_sw
}
```

### `top_perimeter`

value in 49 of 116 examples (70 uses).

Shortest real use, from `adirondack_plank_chair.kdl`:

```kdl
edges top_perimeter
```

### `bottom_perimeter`

value in 19 of 116 examples (21 uses).

Shortest real use, from `flat_pack_scale_test_table.kdl`:

```kdl
edges bottom_perimeter
```

### `front_perimeter`

value in 11 of 116 examples (11 uses).

Shortest real use, from `bedside_cabinet.kdl`:

```kdl
edges front_perimeter
```

## Related terms

- [Bored hole](/almanac/techniques#bored-hole)
- [Chamfer](/almanac/techniques#chamfer)
- [Cockbead](/almanac/techniques#cockbead)
- [Cove moulding](/almanac/techniques#cove-moulding)
- [Dado](/almanac/techniques#dado)
- [Groove](/almanac/techniques#groove)
- [Ogee](/almanac/techniques#ogee)
- [Ogee bracket foot](/almanac/techniques#ogee-bracket-foot)
- [Ovolo](/almanac/techniques#ovolo)
- [Pocket-hole joint](/almanac/techniques#pocket-hole)
- [Rabbet](/almanac/techniques#rabbet)
- [Roundover](/almanac/techniques#roundover)
- [Stopped chamfer](/almanac/techniques#stopped-chamfer)
- [Taper](/almanac/techniques#taper)
- [Thumbnail edge](/almanac/techniques#thumbnail-edge)
- [Tongue and groove](/almanac/techniques#tongue-and-groove)
