# Travel

*Parts that move: slides, hinges, and screws swept as a clearance check*

**Names:** `travel` `body` `pivot`

## When to use

Use it when a part is meant to move and you want the compiler to prove it can: a drawer on its
slides, a door or lid on its hinges, a vise chop on its screw. A `travel` does **not** move
anything in the drawing or change a cut: the document is drawn at `rest` and every part keeps
its one transform. The viewer shows a slider per travel that poses the parts for a look (a view, never the cut list). It adds a check, which carries the moving body through its range and fails
if it meets any other part on the way.

If you only need the hardware counted or recessed, use `hardware_mount` / `hardware_use`
([`hardware-mounting`](hardware-mounting.md)). A part that is *fixed* in a different position (a
drawer drawn half open) is just placed there.

Keywords: slide, drawer, runner, hinge, swing, door, lid, vise, screw, travel, sweep, clearance,
open, extension, pivot, collision.

## Syntax

A `travel` is the one child a `join` may have. The **male** end of the join moves; the female is
the part it is carried on.

```text
join joint=<Joint> male=Part@Mark female=Part@Mark {
    travel [kind=slide|angle] axis=x|y|z|-x|-y|-z [min=<n>] [max=<n>] [rest=<n>] [step=<n>]
           [body=part|assembly] [pivot=<Mark>]
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `kind` | `slide` moves along the axis (mm); `angle` turns about it (degrees, right-handed) | `slide` |
| `axis` | `x`, `y` or `z`, signed to reverse (`-y`), in the frame of the assembly the `join` is written in | required |
| `min`, `max` | the limits, CEL numbers | `min` 0; `max` the item's `travel_distance` (slide) or `max_angle` (angle), else an error |
| `rest` | the pose the document is drawn in; must lie in `[min, max]` | `min` |
| `step` | sweep spacing | 10 mm for a slide, 5 degrees for an angle |
| `body` | `part` moves the male part alone; `assembly` moves every part under its innermost enclosing assembly | `part` |
| `pivot` | `angle` only: a mark **on the male part** the axis passes through | the male end's own mark |

Catalogue numbers a `travel` reads, on the `hardware` item the joinery names: `travel_distance`
(a slide's reach), `max_angle` (a hinge's swing), and `mount_gap`, the gap a slide fills between
the two parts it joins. With a `mount_gap` the check also measures the real gap and fails
(`ww::check::slide_clearance`) if it is more than 0.5 mm from the catalogue's; the margin is
printed by `ww check` and is in its JSON (`invariants.travel.slide_margins`).

## Example

```kdl
project name="Lidded box" units=mm schema_version=1 {
    materials {
        material Oak type=hardwood
        hardware Hinge leaf_length=50.0 leaf_width=16.0 leaf_thickness=2.0 max_angle=110.0
    }
    joinery hardware_mount name=LidHinge { hardware Hinge }
    assembly Box {
        part Case {
            stock Oak 100.0 60.0 20.0
            mark Seat x=0.0 y=60.0 z=0.0 normal=pos_z
            by top_sw
            pos x=0.0 y=0.0 z=0.0
        }
        assembly Lid {
            part Panel {
                stock Oak 100.0 60.0 10.0
                mark Seat x=0.0 y=60.0 z=-10.0 normal=neg_z
                // the hinge pin's line: the lid's back edge, on its underside
                mark Pin x=0.0 y=60.0 z=-10.0 normal=neg_z
                by top_sw
                pos x=0.0 y=0.0 z=10.0
            }
            part Cleat {
                stock Oak 100.0 10.0 10.0
                by top_sw
                pos x=0.0 y=0.0 z=0.0
            }
            // Swings the whole lid (both parts) up to the hinge's 110 degrees.
            join joint=LidHinge male=Panel@Seat female=Case@Seat {
                travel kind=angle axis=x body=assembly pivot=Pin
            }
        }
    }
}
```

A drawer on slides, from `examples/welsh_dresser.kdl`: the drawer is its own assembly, each
side stands on a slide over its runner, and the catalogue gives the reach and the gap:

```text
hardware UndermountSlide_350 travel_distance=300.0 mount_gap=12.0 load_kg=35.0 ...
join joint=DrawerSlide male=DrawerSide_L@Slide female=Runner_L@SlideEdgeL {
    travel axis=-y body=assembly
}
```

## Pitfalls and diagnostics

- **Which end moves.** The male end. `body=part` (the default) moves that one part, which is
  right for a vise chop and wrong for a door: write `body=assembly` and put the door's parts in
  an `assembly`. A drawer is the same: wrap the front, sides, back and bottom in one.
- **A join can only name parts of its own assembly or one enclosing it**, so the slide joins of
  a drawer live in the drawer's assembly and reach out to the runner; the drawer must be a
  nested inline `assembly`, not `part X instance=`.
- **Two joins, one body.** Two hinges on one door each carry the same `travel`; it is swept
  once. Declare it on both so either can be deleted.
- **Where the pin is matters.** The axis passes through the `pivot` mark, or the male end's own
  mark. A hinge mark in the middle of a door's edge puts the pin in the middle of its thickness
  and the door's back corner swings into the carcass; mark the pin on the show face
  (`welsh_dresser.kdl`'s `Pin_Front` / `Pin_Back`).
- **`ww::check::travel_foul`** (defect): a moving part overlaps a fixed one. The message names
  both parts, the first *sampled* travel that fouls, and the onset between that sample and the
  last clean one. A pair already overlapping at `rest` is `ww clash`'s finding and is skipped.
- **`ww::check::travel_invalid`** (defect): the body moves nothing, `min` is above `max`, `rest`
  is outside the range, the pivot mark does not exist, or there is no `max` to default.
- **`ww::check::slide_clearance`** (defect): the gap a slide fills is not its `mount_gap`.
- **`ww::check::travel_unchecked`** (defect): the kernel could not intersect a pair; it is not
  reported clean.
- **`ww::expand::bad_travel`** (error): an unknown property, axis, kind or body word, a missing
  `axis=`, or `pivot=` on a slide.
- **Sampling is not a proof.** An obstacle thinner than `step` between two samples is missed;
  shrink `step=`. The default (10 mm, 5 degrees) is below every board in the library.
- **Hardware is never swept.** A screw, garter, guide, hinge or slide is a catalogue item and a
  display proxy, never a part, so it cannot foul anything. Model what must be checked as a part.
- **No dynamics.** No forces, wear, springs, door sag or thread geometry (ADR-0075).
- The viewer does not yet have a slider per movable part (#225); the data (axis, limits, rest, moving
  part ids) is in `PlacedJoin::travel`.

## See also

- `docs/adr/0075-travel-primitive.md`; `docs/rfc/0081-vise-and-moving-hardware.md`,
  `docs/rfc/0050-drawer-runners-and-slides.md`, `docs/rfc/0071-hardware-hinges.md`
- `docs/KDL_AUTHORING.md` §5 (`hardware`) and §8 (`join`)
- `examples/workbench.kdl` (a vise chop, 0 to 180 mm), `examples/welsh_dresser.kdl` (slides and
  door swings), `examples/blanket_chest.kdl` (a lid swung 100 degrees)
- [`hardware-mounting`](hardware-mounting.md), [`joinery-contract`](joinery-contract.md)

## Names

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

### `travel`

node in 3 of 116 examples (8 uses).

Shortest real use, from `welsh_dresser.kdl`:

```kdl
travel axis=-y body=assembly
```

### `body`

word-valued property; property in 2 of 116 examples (7 uses).

Shortest real use, from `welsh_dresser.kdl`:

```kdl
travel axis=-y body=assembly
```

### `pivot`

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

Shortest real use, from `welsh_dresser.kdl`:

```kdl
travel kind=angle axis=z body=assembly pivot=Pin_Back
```

