# Cut settings: full coverage, and a way to tell galvo from gantry
`GET /api/…cuts` reported 16 of the roughly 75 settings the application's
cut-settings editors actually write. This closes that gap, and restructures
the payload so a client can tell which settings apply to the attached machine.
LightBurn only — MillMage responses are unchanged.
Every field below was verified against a running build on both a gantry and a
galvo device profile, with no machine physically connected.
The API remains pre-stable (see [`docs/versioning.md`](docs/versioning.md)), so
the breaking items ship without a version bump.
---
## Action required
Three items change the shape or meaning of what you already read. Everything
else is additive.
### 1. Machine-specific settings moved into a nested block
`LaserCutParams` now contains **exactly one** of `params.galvo` or
`params.gantry`, matching a new `profile` field on the entry.
```jsonc
// before — one flat bag, no way to know what applied
{ "params": { "speed": 200, "frequency": 20000, ... } }
// after
{
"profile": "gantry",
"params": {
"speed": 200, "frequency": 20000, "override_frequency": false,
"gantry": { "run_blower": true, "enable_ppi": false, "ppi": 100, ... }
}
}
```
Why it had to change: the project stores **both** sets of machine settings
whatever device is selected, so a cut setting is not intrinsically galvo or
gantry. A flat payload would have put `jump_speed` next to `ppi` with no way
to know which the machine reads.
`profile` is `galvo`, `gantry`, or `unknown` when no device profile has been
set up. It comes from the selected device profile, so it is correct with
nothing plugged in.
> **Don't cache the response shape.** Switching the active device changes
> `profile`, and the block that comes with it, for an unchanged project.
### 2. `start_delay` / `end_delay` moved into the blocks
They share storage in the application but mean different things — **dot
delays** on a galvo, **pauses** on a gantry. Rather than appear once with an
ambiguous meaning, they now appear in each block under the meaning that
applies there. Read them from `params.galvo` / `params.gantry`.
### 3. `POST` rejects the wrong machine block
Sending `params.galvo` to a gantry device (or the reverse) is a **400**, not a
silent no-op. Same for `global_passes` on a gantry device.
This is deliberate: silently ignoring a block would let an integration believe
it had configured a galvo when nothing happened. If you build a request from a
cached shape, check `profile` first.
---
## Fixed
Two cases where the old response was not just incomplete but misleading.
**`frequency` had no gate.** `override_frequency` wasn't reported, so you
could not tell whether a layer applied its frequency or inherited the device
default — and `POST`ing `frequency` could silently do nothing, because the
gate stayed false. Set both together when writing.
**`global_passes` was missing entirely.** On a galvo it repeats the whole
sub-layer stack, so real passes are `global_passes` x the per-layer
`num_passes`. Any client computing total passes from the response was low by
that factor. It's now reported on galvo entries and writable.
---
## New: which cuts are actually in use
Every LightBurn cut entry now carries `in_use` and `shape_count`.
Previously the only way to answer "which cut settings does this project
actually use?" was to join `GET /api/layers` on `layer_index`. That gives the
wrong answer when a normal cut and an image cut share a layer — the layer
reports the combined count and can't say which cut the shapes belong to.
Put a vector shape and an image both on C00:
| Source | Reports |
|--------|---------|
| cut entry, `kind: "normal"` | `shape_count: 1` |
| cut entry, `kind: "image"` | `shape_count: 1` |
| `GET /api/layers`, layer 0 | `shape_count: 2` |
The entry counts are per cut setting and span every page of the project, and
they're the same numbers the application filters its own cut list on. This
also brings LightBurn in line with MillMage, which already reported
`shape_count`.
Purely additive — nothing breaks if you ignore it.
---
## New fields
### `params.galvo` (14)
Timing constants `laser_on_tc`, `laser_off_tc`, `end_tc`, `polygon_tc` with
their `override_timings` gate; `jump_speed`, `min_jump_delay`,
`max_jump_delay`, `jump_dist_limit`; `start_delay`, `end_delay`;
`wobble_enable`, `wobble_step`, `wobble_size`.
### `params.gantry` (23)
Air assist (`run_blower`, `auto_blower`, `blower_speed_override`,
`blower_speed_percent`); cut-through (`cut_through_start`, `cut_through_end`,
`through_power`, `through_power_2`); `start_delay`, `end_delay`; leads
(`lead_in`, `lead_out`, `lead_angle`, `lead_length`, `lead_style`);
`enable_ppi`, `ppi`; dot mode (`dot_mode`, `dot_time`, `dot_spacing`);
`force_constant_power`, `dcc_value`, `overcut`.
### Common `params`
`override_frequency`, `z_per_pass`, `u_offset`, `angle_per_pass`, `scan_opt`
(`individual` / `by_group` / `merge_all`), `angle`, `interval`, `dot_width`,
`use_dot_correction`, `ramp_length`, `ramp_outer`, `perforate`, `perf_len`,
`perf_skip`, `tabs_enabled`, `overscan`, `overscan_percent`, `flood_fill`,
`auto_rotate`, `min_power_2`, and the `default_speed` / `default_power1` /
`default_power2` flags.
### Cut level
`global_passes` (galvo), `negative`, `pass_through`, `enable_cleanup`,
`sort_within_layer`, and a `tabs` object (`enabled`, `manual`, `size`,
`use_spacing`, `count`, `count_max`, `limit_max`, `spacing`, `skip_inner`,
`cut_power`).
### Image cuts
`cells_per_inch`, `halftone_angle`, `link_dpi_to_interval`.
---
## Files
| File | Change |
|------|--------|
| `docs/openapi.yaml` | `LaserCut` and `LaserCutParams` rewritten; `LaserGalvoParams` and `LaserGantryParams` added; `POST` body and prose updated |
| `docs/cut-settings.md` | New sections on `profile`, the machine blocks, and which cuts are in use; write-side field list extended |
| `docs/api-reference.md` | Machine-differences convention |
| `CHANGELOG.md` | All of the above |
---
## Compatibility
Additive changes are safe for clients that ignore unknown fields and tolerate
new enum values, as [`docs/versioning.md`](docs/versioning.md) asks. The three
items under **Action required** are not: they relocate existing fields and
tighten what `POST` accepts.
This is the second release in a row to change `params` / `sub_layers`. If you
maintain an integration against these endpoints, this is the one to read
before upgrading.