# Curved Members

*A swept bend along a member's own length*

**Names:** `bend` `arc` `spline`

## When to use

- A member that curves along its own **length**, not its cross-section: a chair crest rail
  that bows in plan or in elevation, a splat that follows a lumbar curve — RFC-0040's own two
  motivating cases.
- You want the cut list to say "follows spine," and the card to dimension the curve itself
  (an arc's radius, or a spline's keyframes), not the swept solid's bounding box.

Not for a cross-section that varies along a *straight* length (that is `section`'s own
`rect`/`interp=spline` job — [`turning-and-sections`](turning-and-sections.md)), a doubly
curved surface such as a saddled seat (not built, RFC-0021), or a true 3D (non-planar) spine
(not built: `plane=` is always one coordinate plane).

## Syntax

```text
bend axis=z plane=<xz|yz> [tolerance=<expr> | samples=<expr>] {
    arc radius=<expr>
}
bend axis=z plane=<xz|yz> [tolerance=<expr> | samples=<expr>] {
    spline {
        point z=<expr> <x|y>=<expr>   // two or more, z running the whole part
    }
}
```

- `axis=` — only `"z"`. A bent member's own length is always its local `z`; place it with
  `rot` like any other component whose length is not world `z`.
- `plane=` — `"xz"` (bows local `x`, holds local `y` fixed) or `"yz"` (bows local `y`, holds
  local `x` fixed). The spine stays planar.
- `tolerance=` or `samples=`, mutually exclusive, same meaning as the `rect`-section family's
  own knobs.
- Exactly one of `arc` (closed-form, tangent at `z=0` only, asymmetric end to end) or `spline`
  (two or more `(z, offset)` control points, resampled through `ww_geom::spline::sample` —
  symmetric when both ends hold `offset=0`). A spline's points must run the whole part, `z=0`
  to `z = 0 - size.z`.
- A radius (or local curvature) tighter than the cross-section's own half-depth is refused
  before any kernel call — it would self-intersect the sweep.

**Available faces.** Only the two end `FaceFrame`s (and their standard anchors — `top_sw`, …,
`top_center`, `bottom_center`, `center`) exist; the swept lateral surface is not a face. A
`mark` with an end-facing `normal=` (`pos_z`/`neg_z`) resolves off the real, curved end frame;
one with a lateral `normal=` is refused by name. `mortise_and_tenon` needs a `mark` with
`normal=` (a standard anchor has no facing, D6) — a mark at the end face's own centre is exact
whenever the curve holds `offset=0` at that end.

## Example

```kdl fragment=component
bend axis=z plane=xz {
    spline {
        point z=0.0          x=0.0
        point z="0.0 - 300.0" x="0.0 - 18.0"
        point z="0.0 - 600.0" x=0.0
    }
}

mark CrestTenon_Start x=50.0 y=50.0 z=0.0            normal=pos_z
mark CrestTenon_End   x=50.0 y=50.0 z="0.0 - 600.0"  normal=neg_z
```

`examples/arched_crest_side_chair.kdl` is the worked case: a steam-bent ash crest rail,
mortise-and-tenoned into its two back posts' own end faces — the same "square tenon against a
resolved `FaceFrame`" path a straight member's own end joinery uses, no synthesizer change.
`crates/ww-build/tests/bend_joinery.rs`'s own fixture is the `arc`-bodied, tangent-at-one-end
case.

## Pitfalls and diagnostics

- `axis=x`/`axis=y`, a varying cross-section along the bend, a mid-curve feature or mark, a
  curved CNC toolpath, and turning a bend (round stock plus a curved spine) are all not
  built.
- A spline whose points do not run the whole part (`z=0` to `z = 0 - size.z`) is refused:
  "a `bend`'s spline must run the whole part, from its top face (z=0) to its foot ...".
- A `join` naming a lateral mark on a bent component is refused, naming the two valid end
  faces (`top_sw`/`bottom_sw`'s own families).
- `mortise_and_tenon` against a standard anchor (`top_center`, …) is refused: "is a standard
  anchor, which has a point but no facing (D6)" — use a `mark` with `normal=` instead.
- The built solid's real boundary can stand a millimetre or two past the exact plane the end
  `FaceFrame` reports, at a member's own free ends (an existing, documented margin,
  `crates/ww-build/tests/bend_joinery.rs`'s own header) — a mating part placed flush against
  that reported plane can show a small, bounded clash there; not a placement bug.

## See also

`docs/KDL_AUTHORING.md` §6 ("`bend`"), RFC-0040, ADR-0016 (`arc`/`path2d` tessellation),
ADR-0032 (`ww_geom::spline`), ADR-0012 (native/wasm determinism — a bend's own rotation uses
`libm::atan2`/`libm::sincos`, never the inherent `f64` methods), ADR-0028 (grain axis —
"follows spine"), [`turning-and-sections`](turning-and-sections.md) (the sibling
cross-section-along-a-straight-length family), [`joinery-tenons`](joinery-tenons.md).

## Names

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

### `bend`

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

Shortest real use, from `arched_crest_side_chair.kdl`:

```kdl
bend axis=z plane=xz {
    // A `bend`'s spline must run the whole part from its own top face (z=0) to its
    // foot (z = 0 - open_w, D4's top-down local convention) — the same direction
    // `crates/ww-build/tests/bend.rs`'s own spline fixture uses, not the ascending
    // z this file's own first draft tried (kdl-check's own diagnostic caught it).
    spline {
        point z=0.0                     x=0.0
        point z="0.0 - (open_w / 2.0)"  x="0.0 - crest_arch_rise"
        point z="0.0 - open_w"          x=0.0
    }
}
```

### `arc`

node in 19 of 116 examples (65 uses).

Shortest real use, from `windsor_fan_back_side_chair.kdl`:

```kdl
arc center_x=rf center_y=cy radius=rf start_angle=0.0 end_angle=180.0
```

### `spline`

node in 26 of 116 examples (110 uses); value in 28 of 116 examples (42 uses).

Shortest real use, from `adirondack_plank_chair.kdl`:

```kdl
spline { point x=0.0 y=60.0; point x="arm_w * 0.12" y=18.0; point x="arm_w * 0.5" y=0.0; point x="arm_w * 0.88" y=18.0; point x=arm_w y=60.0 }
```

## Related terms

- [Bent lamination](/almanac/techniques#bent-lamination)
- [Steam bending](/almanac/techniques#steam-bending)
