# Paths & Splines

*Spline segments, named compound paths, and derived circle paths*

**Names:** `path` `cut_path` `spline` `circle` `line` `arc` `polyline`

## When to use

A shaped edge or a pierced cut-out that is not a straight line or a round: a cloud lift on
an apron, a serpentine rail, a vase-shaped splat, a scalloped edge, a freeform window. Use
`spline` for a smooth curve through points you choose, `line`/`arc`/`polyline` for the
straight and circular parts, and chain them in one `cut_path` (a compound path). Name a
compound path once with `path <Name> { ... }` when several `cut_path`s reuse it. Use `circle`
for a plain round hole of any size (a `hole` is simpler for a drilled one).

## Syntax

```kdl grammar
cut_path depth= [face=] [keep="outside"|"inside"] [path=Name] { <segment> ... }
path Name <kind> <props>            // one named segment
path Name { <segment> ... }         // a named compound path
line   from_x= from_y= to_x= to_y=
arc    center_x= center_y= radius= start_angle= end_angle=
polyline { point x= y= ; ... }
spline [closed=#true] [tolerance=<mm> | samples=<n>] { point x= y= ; ... }
circle center_x= center_y= radius=
```

- `spline` passes through every `point`. `tolerance=` (default `0.01` length units) is the
  largest gap between the cut edge and the true curve; `samples=` instead fixes the chords per
  span (whole number, at most 1024). Give one, not both. The curve is flattened to chords once,
  the same way every time, so the result is deterministic and native/wasm identical.
- An open spline closes along its chord, like a lone `arc`; in a compound path it chains end to
  start (each segment's end must meet the next one's start). `closed=#true` is a whole loop and
  cannot chain. A `circle` is a whole loop too.
- A `cut_path` removes the closed region (`keep="outside"`, the default); `keep="inside"`
  keeps only the region, making it the part's new footprint.

## Example

```kdl
// A cloud lift cut into the lower edge of an apron: straight sides, a spline between them.
project name="Cloud lift" units=mm schema_version=1 {
    materials { material Oak type=hardwood }

    component Apron {
        stock Oak 190.0 22.0 90.0

        // Named once as a compound path, then cut.
        path Cloud {
            line from_x=30.0 from_y=-10.0 to_x=30.0 to_y=0.0
            spline {
                point x=30.0 y=0.0
                point x=75.0 y=34.0
                point x=115.0 y=34.0
                point x=160.0 y=0.0
            }
            line from_x=160.0 from_y=0.0 to_x=160.0 to_y=-10.0
        }
        cut_path depth=0.0 face=neg_y path=Cloud

        // A closed spline: a pierced, smooth-edged window, finer than the default.
        cut_path depth=0.0 face=neg_y {
            spline closed=#true tolerance=0.05 {
                point x=60.0 y=45.0
                point x=80.0 y=65.0
                point x=100.0 y=45.0
                point x=80.0 y=25.0
            }
        }

        // A round hole by centre and radius.
        cut_path depth=0.0 face=neg_y { circle center_x=20.0 center_y=65.0 radius=8.0 }
    }

    assembly Rail {
        part Apron instance=Apron { pos x=0.0 y=0.0 z=0.0 }
    }
}
```

## Pitfalls and diagnostics

- The region must have an enclosed area and not cross itself. A wave that dips below its own
  chord is reported as a skip ("the closed path crosses itself"); lift the end points or close
  the loop yourself with a `line`.
- Cut corners must lie *outside* the stock so the cut breaks cleanly through the edge: the
  cloud lift's low corners are at `y=-10`, below the true edge at `y=0`.
- Consecutive spline points that coincide are refused. A curve of `closed=#true` needs at
  least three points, an open one at least two.
- A spline is drawn through the points you give, so overshoot comes from the points, not the
  maths: space them evenly, and add a point where the curve bends sharply.
- `closed=#true` combined with other segments, `tolerance=` together with `samples=`, a
  non-whole `samples=`, and a tolerance so fine that a span would need more than 1024 chords
  are all refused with the cause named.
- `spline` and `circle` are path segments, not features: written outside a `cut_path`/`path`
  they are reported as such.
- `kdl-check` passing says only that the file is well formed; it cannot see whether a path
  closes or is sensible. Run `ww check`.
- Not built: a bent or curved *part* (RFC-0040), G02/G03 toolpath arcs, features placed along a
  path.

## See also

- `docs/KDL_AUTHORING.md` §7, `cut_path`
- `docs/adr/0032-spline-and-compound-paths.md`, `docs/adr/0016-path-geometry.md`
- `docs/rfc/0120-curved-path-geometry.md`
- `docs/agent-reference/nodes.md` (`cut_path`), `tasks.md` ("cut a curved outline")

## Names

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

### `path`

feature, word-valued property; node in 9 of 116 examples (15 uses); property in 9 of 116 examples (19 uses).

Shortest real use, from `jacobean_joint_stool.kdl`:

```kdl
path ArchLong {
    line from_x="span_w * 0.10" from_y=-5.0 to_x="span_w * 0.10" to_y=0.0
    spline {
        point x="span_w * 0.10" y=0.0
        point x="span_w * 0.30" y="arch_rise * 0.80"
        point x="span_w * 0.50" y=arch_rise
        point x="span_w * 0.70" y="arch_rise * 0.80"
        point x="span_w * 0.90" y=0.0
    }
    line from_x="span_w * 0.90" from_y=0.0 to_x="span_w * 0.90" to_y=-5.0
}
```

### `cut_path`

feature; node in 50 of 116 examples (239 uses).

Shortest real use, from `ming_waisted_corner_leg_table.kdl`:

```kdl
cut_path depth=0.0 face=neg_y path=Hoof
```

### `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 }
```

### `circle`

node in 40 of 116 examples (933 uses).

Shortest real use, from `windsor_round_stool.kdl`:

```kdl
circle r=rf
```

### `line`

node in 33 of 116 examples (256 uses).

Shortest real use, from `postmodern_flat_silhouette_chair.kdl`:

```kdl
line from_x=w from_y=0.0 to_x=w to_y=seat_h
```

### `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
```

### `polyline`

node in 21 of 116 examples (91 uses).

Shortest real use, from `incised_walnut_washstand.kdl`:

```kdl
polyline { point x="9.0 + (0.0000)" y="9.0 + (1.0000)"; point x="face_w - 9.0 + (0.0000)" y="9.0 + (1.0000)"; point x="face_w - 9.0 - (0.0000)" y="9.0 - (1.0000)"; point x="9.0 - (0.0000)" y="9.0 - (1.0000)" }
```

## Related terms

- [Bootjack](/almanac/techniques#bootjack)
- [Cut path](/almanac/techniques#cut-path)
