# Components & Stock

*Grain direction and marks for joining*

**Names:** `component` `stock` `role` `grain` `mark` `normal` `tilt` `heading` `lengthwise` `crosswise`

## When to use

Every distinct piece of timber or sheet is one `component` (a reusable template) with exactly
one `stock` line, plus the `mark`s that joints attach to. Use a `part` with an inline `stock`
(see `parts-and-assemblies`) for a one-off that nothing else reuses. Turned (round) stock
and its `section` profile are in `round-stock-sections`.

## Syntax

```text
component <Name> [role=<free text>] {
    [params { ... }]                              // defaults, overridable per instance
    stock <Material> <x> <y> <z> [lengthwise|crosswise]   // REQUIRED, exactly one
    mark <Name> x=<expr> y=<expr> z=<expr> normal=<face> [tilt=<expr> heading=<expr>]
    <features...>                                  // see edge-and-face-cuts, cut-paths
}
```

- **`stock <Material> x y z`**: the material name, then three positional dimensions (floats
  or CEL), then an optional grain word. `x` is width, `y` is depth, `z` is length down the
  part (the part's own frame, ADR-0007). **Exactly one per component.** A `part` has either
  `instance=` or an inline `stock`, never both.
- **Grain** (`lengthwise` default, along the longest dimension; or `crosswise`). State it where
  it decides a joint. A `type=sheet` material takes no grain (hard error). The grain word
  vocabulary and the style finish rule are a separate family; see `INDEX.md`.
- **`role=`** is free text for BOM grouping (`leg`, `apron`, `rail`, `stile`, `panel`, `top`,
  `shelf`, `door`, `drawer_face`, ...). It has no compiler meaning, except that
  `role=reference` makes a part a reference part (see `parts-and-assemblies`).
- **`mark`** is a named datum: a point plus a facing, attached to the part's own frame. Joints
  address it as `Part@Mark`. `normal` is one of `pos_x neg_x pos_y neg_y pos_z neg_z` and
  points **out of the material**, toward the mating part. Write coordinates in the
  component's own params (`x=leg_dim`, `y="apron_th / 2.0"`), never as bare constants.
- **`tilt=` / `heading=`** (RFC-0022, built in M25) make a mark an *oriented reference*: the
  facing leans `tilt` degrees off the named face's outward direction, toward `heading`
  degrees counter-clockwise from that face's own `+u`. Both default to `0.0` and then the
  mark is exactly the named face. Use them for a mitred end, a compound-angle approach or a
  spoke at an azimuth. A mark that sits *set back* from a face but still faces squarely along
  an axis needs neither. Worked examples: `examples/wagon_wheel.kdl` (`Hub`, `Felloe`).
- `params` inside a component are defaults an instance can override with child nodes on its
  `part` line.

## Example

```kdl
project name="Leg Frame" units=mm schema_version=1 {
    params {
        leg_dim 45.0 min=30.0 max=70.0 step=1.0 label="Leg" category="Stock"
        leg_len 700.0 min=400.0 max=900.0 step=10.0 label="Leg length" category="Envelope"
        felloe_half_angle 18.0
    }
    materials { material Ash type=hardwood }
    component Leg role=leg {
        stock Ash leg_dim leg_dim leg_len lengthwise
        mark MortiseFace_X x=leg_dim y="leg_dim / 2.0" z=-55.0 normal=pos_x
        // an oriented mark: leans off pos_x by the mitre's half angle
        mark Spoke_1 x=leg_dim y="leg_dim / 2.0" z=-300.0 normal=pos_x tilt=felloe_half_angle heading=0.0
    }
    assembly Root {
        part Leg_A instance=Leg { by top_sw; pos x=0.0 y=0.0 z=0.0 }
    }
}
```

## Pitfalls and diagnostics

- A component declared and never instantiated by a `part` is a `kdl-check` **warning**. It is a
  real bug that has happened: a Welsh dresser's centre support was declared with marks and
  never placed, so the shelf span was more than double what the file claimed.
- A mark not on any real stock face fails **at `join` time**, not at the `mark` line. With
  `tilt=`/`heading=` this now covers a mitred end or compound corner that is *within* the
  stock. Still not built: a **mark on a curved turned surface** (only the flat axis ends,
  `normal=pos_z`/`neg_z`, are reachable) and a mortise cut into a curved face. Tenon into the
  square pommel instead (see `round-stock-sections`). A **compound mitre** joint
  (`miter` that leans and turns) is also not synthesised; see `joinery-frames`.
- A mark's coordinates are in the component's frame *before* any `rot`; the placement is the
  part's `by`/`pos`/`rot`, not the mark's.
- A `component` with no `stock` node is a `kdl-check` error ("has no `stock` node"). Write
  exactly one (KDL_AUTHORING §6); the position of the line among the features does not matter.
- Mark names and `id=`s inside a `repeat` must interpolate the loop variable
  (`mark "Pin_{i}" ...`), or they collide (`kdl-check` enforces this).
- `shape=round` takes no grain argument; naming one is rejected.

## See also

- `docs/KDL_AUTHORING.md` §6 (`component`, `mark`), §7b (naming), ADR-0007 (the stock line).
- `docs/rfc/0022-compound-angle-joint-frame.md` (resolved; the residual gaps above).
- `parts-and-assemblies.md` (placing a component), `joinery-contract.md` (what marks are for),
  `round-stock-sections.md` (turned stock).

## Names

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

### `component`

project-level node; node in 110 of 116 examples (819 uses); value in 1 of 116 examples (1 uses).

Shortest real use, from `glazed_vitrine_whiplash_crest.kdl`:

```kdl
component Pane role=glass {
    stock Glass dw 6.0 dh
}
```

### `stock`

feature; node in 114 of 116 examples (910 uses).

Shortest real use, from `glazed_vitrine_whiplash_crest.kdl`:

```kdl
stock Glass dw 6.0 dh
```

### `role`

word-valued property; property in 114 of 116 examples (900 uses).

Shortest real use, from `glazed_vitrine_whiplash_crest.kdl`:

```kdl
component Pane role=glass {
    stock Glass dw 6.0 dh
}
```

### `grain`

feature, word-valued property; property in 2 of 116 examples (3 uses).

Shortest real use, from `biedermeier_pedestal_table.kdl`:

```kdl
stock Cherry shape=round diameter=top_diameter z=top_thickness grain=across
```

### `mark`

feature; node in 110 of 116 examples (2193 uses).

Shortest real use, from `windsor_fan_back_side_chair.kdl`:

```kdl
mark Top x=7.5 y=7.5 z=0.0 normal=pos_z
```

### `normal`

word-valued property; property in 110 of 116 examples (2207 uses).

Shortest real use, from `windsor_fan_back_side_chair.kdl`:

```kdl
mark Top x=7.5 y=7.5 z=0.0 normal=pos_z
```

### `tilt`

property; property in 2 of 116 examples (6 uses).

Shortest real use, from `primary_grid_lath_armchair.kdl`:

```kdl
mark Seat x=0.0 y="lath / 2.0" z="0.0 - dm" normal=neg_x tilt=0.01 heading=0.0
```

### `heading`

property; property in 2 of 116 examples (6 uses).

Shortest real use, from `primary_grid_lath_armchair.kdl`:

```kdl
mark Seat x=0.0 y="lath / 2.0" z="0.0 - dm" normal=neg_x tilt=0.01 heading=0.0
```

### `lengthwise`

grain; value in 26 of 116 examples (84 uses).

Shortest real use, from `ming_horseshoe_armchair.kdl`:

```kdl
stock Huanghuali length th h lengthwise
```

### `crosswise`

grain; not used by any example yet.

