Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ JSON field names and exit codes are part of the public API.

## [Unreleased]

### Added
- Two-filament jobs with an automatic AMS colour change at a layer height:
- `slice --filament P1 --color C1 --filament P2 --color C2 --filament-change-z Z` (`--filament` and `--color` repeat once per filament; `--colour` is an alias). Filament 2 starts on the first layer above Z, which must be a layer top. `slice` reads the change back from the G-code and exits 11 without writing to the output dir if Bambu Studio didn't place it.
- `preflight`/`print send` take one `--slot` per filament, in filament order. Each filament's type, profile, amount and temperature gates run against its own slot. `ams_mapping` maps each filament to its slot (e.g. `[3, 0]`). The same slot given twice exits 2.
- `filament load --slot S --confirm [--timeout 5m]`: unload the toolhead and load AMS slot S, e.g. a remote colour change at a pause. It runs only while paused or IDLE/FINISH/FAILED. When paused, the toolhead must hold an AMS filament and the slot must hold the same material. It exits 0 only once the printer reports S in the toolhead with the change finished. It exits 14 if that's unconfirmed and 1 if a new HMS code or print error appears during the change.
- JSON fields (additive):
- `status.ams_status`
- slice summary: `filament_changes`, `prime_tower`, `filament_presets`, and per-filament `model_g`/`waste_g`/`nozzle_temp`/`nozzle_temp_range`
- preflight: `slots`/`tray_ids`, and `filament` on per-filament gate rows of multi-filament jobs
- job: `filament_changes`

### Changed
- `preflight` `single_filament` now fails only when the number of `--slot`s differs from the number of filaments in the plate. Single-filament jobs send the same payload as before.

### Fixed
- `print resume --confirm` exited 0 even when the printer was still paused 3 s later. It now exits 0 only once the printer reports RUNNING (`--wait`, default 30s, must be positive; otherwise exit 14). It also refuses (exit 9) during a filament change, and on X1-series printers when the AMS reports an empty toolhead.
- Per-filament temperatures in multi-filament 3MFs are read through `filament_self_index`/`filament_extruder_variant` instead of plain indexing, which reads the wrong row whenever an earlier filament has more than one nozzle-variant row.

## [0.2.0] - 2026-10-06

### Changed
Expand Down
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,21 @@ bambu print send part-solid-pla.gcode.3mf --slot 4 --confirm
bambu monitor --snapshot-at-layer 2 --exec 'notify-send "$BAMBU_EVENT $BAMBU_STATE"'
```

### Two colours

A two-filament job changes colour at a layer height, and the AMS swaps by itself: no pause, nobody at the printer.

```bash
bambu slice sign.3mf --recipe prototype-pla \
--filament "Generic PLA" --color FFFFFF --filament "Bambu PLA Matte" --color 000000 \
--filament-change-z 4.0 # white through Z 4.0, black from the next layer up
bambu print send sign-prototype-pla.gcode.3mf --slot A4 --slot A1 --dry-run # one slot per filament, in order
```

- `--filament-change-z` must be a layer top. `slice` reads the change back from the G-code and fails (exit 11) if Bambu Studio didn't place it.
- The summary has grams per filament, including purge and prime-tower waste, and `filament_changes`.
- To change colour by hand at a pause (e.g. a spool that isn't in the job), `bambu filament load --slot A1 --confirm` swaps the toolhead to another AMS slot remotely, then `bambu print resume --confirm`.

### Recipes

Recipes name presets generically (`"0.20mm Standard"`, `"Bambu PLA Basic"`), and `bambu` resolves the right system preset for your printer model and nozzle.
Expand All @@ -115,13 +130,16 @@ Recipes name presets generically (`"0.20mm Standard"`, `"Bambu PLA Basic"`), and
1. **`preflight` checks before anything is sent:**
- the printer is idle, with no blocking errors (informational HMS codes like "Inspecting first layer" are ignored)
- Developer Mode is on and an SD card is present
- the 3MF is intact and sliced for this printer model, with a single filament
- the 3MF is intact and sliced for this printer model, with one `--slot` per filament
- the nozzle diameter and plate match
- the slot is loaded with the same filament type (a different product of that type is a WARN)
- there's enough filament (unknown remaining is a WARN), and the temperatures are sane
- each slot is loaded with its filament's type (a different product of that type is a WARN)
- each slot has enough filament (unknown remaining is a WARN), and the temperatures are sane
- the printer's AI protections are on (WARN only) and FTPS login works
2. **`print send`** re-runs preflight and refuses on any FAIL. It requires `--confirm`, uploads over FTPS, reads the file back to verify its MD5, then starts the job with bed leveling, flow calibration, vibration compensation and first-layer inspection **on**.
3. **`print pause|resume|stop`** require `--confirm`. `bambu` has no command that changes printer settings.
3. **`print pause|resume|stop`** require `--confirm`. `resume` exits 0 only once the printer reports RUNNING. It refuses mid-filament-change, and with an empty toolhead on the X1 series.
4. **`filament load`** requires `--confirm` and works only while paused or idle. When paused, the toolhead must hold an AMS filament and the slot must hold the same material. It exits 0 only once the printer reports that slot in the toolhead.

`bambu` has no command that changes printer settings.

### Exit codes

Expand Down
35 changes: 28 additions & 7 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ In v0.1:
- Headless slicing (STL/3MF/OBJ) with Bambu Studio system presets, flattened by us, plus built-in and user recipes.
- Preflight safety gates.
- LAN send: implicit FTPS upload, read-back MD5, then the MQTT `project_file` command.
- Pause / resume / stop.
- Pause / resume / stop. `resume` succeeds only once the printer reports RUNNING.
- Two-filament jobs: a layer-height colour change that the AMS performs from the G-code, one `--slot` per filament.
- `filament load`: a remote AMS colour change while paused (the manual route when the second colour isn't in the job).
- Status, and a streaming `status --watch`.
- `monitor` with a layer-N snapshot and an exec hook.
- Camera snapshot for RTSPS models (X1/X1E/H2/P2S/X2D) via ffmpeg.
- Introspection: `schema`, `exit-codes`, `version`, `auth status`.

Out of scope for v0.1:
- Cloud API and Bambu Connect.
- Multi-material/multi-color send: preflight refuses jobs with more than one filament.
- Per-object or painted multi-colour slicing: `slice` makes multi-filament jobs only from layer-height changes.
- STEP input (convert to STL/3MF first).
- The P1/A1 camera, which uses the port-6000 JPEG protocol: a clear error for now.
- Changing any printer setting. `bambu` never changes printer settings: no xcam/AI toggles, no lights, no temperatures.
Expand Down Expand Up @@ -49,9 +51,11 @@ Top-level verbs are kept for the high-frequency, single-purpose actions an opera
```
bambu status [--watch] # read-only; --watch streams NDJSON until Ctrl-C
bambu slice <model> --recipe R [--filament PRESET] [--set k=v]... [--name N] [--out DIR] [--auto-orient]
bambu preflight <file.gcode.3mf> --slot S [--no-ftp]
bambu print send <file.gcode.3mf> --slot S (--dry-run | --confirm) [--timelapse] [--wait 180s]
bambu print pause|resume|stop --confirm
bambu preflight <file.gcode.3mf> --slot S [--slot S2]... [--no-ftp]
bambu print send <file.gcode.3mf> --slot S [--slot S2]... (--dry-run | --confirm) [--timelapse] [--wait 180s]
bambu print pause|stop --confirm
bambu print resume --confirm [--wait 30s] # exit 0 only once the printer reports RUNNING
bambu filament load --slot S --confirm [--timeout 5m] # paused or idle; exit 0 only once S is in the toolhead
bambu monitor [--snapshot-at-layer N] [--snapshot-dir D] [--exec CMD] [--timeout 2h]
bambu camera snapshot [-o file.jpg]
bambu printer add <name> --host H --serial S [--model X1C] [--nozzle 0.4] [--plate textured_plate] [--default]
Expand Down Expand Up @@ -155,6 +159,15 @@ Precedence:

**Control:** `{"print":{"sequence_id":"<n>","command":"pause"|"resume"|"stop","param":""}}` at QoS 1.

**Filament load** (Bambu Studio `command_ams_change_filament`; verified against X1C firmware 01.12 on 2026-10-08 during a
pause, A4 → A1): `{"print":{"sequence_id":"<n>","command":"ams_change_filament","ams_id":A,"slot_id":S,"target":A*4+S,
"curr_temp":<mid of loaded tray's range>,"tar_temp":<mid of target tray's range>}}` (210 when a tray has no range).
Done when `ams.tray_now` is the target and `ams_status >> 8` is no longer `0x01` (filament_change) nor the stage a
loading/unloading one.

**Two-filament `ams_mapping`:** one entry per project filament in filament order, e.g. `[3, 0]` for A4 then A1
(Studio's "v0" format, `SelectMachine.cpp`). Not yet verified on a real print.

**HMS:**
- Code = `%04X_%04X_%04X_%04X` of attr>>16, attr&0xFFFF, code>>16, code&0xFFFF.
- Module = attr>>24; severity = code>>16 (1 fatal, 2 serious, 3 common, 4 info).
Expand Down Expand Up @@ -188,6 +201,11 @@ Resources are the first of `../Resources`, `../resources`, `../share/BambuStudio
**Invocation:**
- Absolute `--outputdir` (a work dir under the user cache). Bambu Studio chdirs into its bundle, so relative paths break.
- `--slice 0 --arrange 1 --orient 0|1 --load-settings "m.json;p.json" --load-filaments f.json --export-3mf <name>.gcode.3mf <model>`
- Two or more filaments: `--load-filaments "f.json;f_2.json"`, `--filament-colour "#RRGGBB;#RRGGBB"` (Studio sizes the
purge from the colours) and `--load-custom-gcodes c.json` holding
`{"mode":"MultiAsSingle","gcodes":[{"type":"ToolChange","print_z":<first layer top above Z>,"extruder":2,"color":"#RRGGBB","extra":""}]}`,
the change the GUI's layer slider stores. Studio drops a ToolChange silently when the mode doesn't fit, so `slice`
reads the `T<n>` changes back from the G-code and exits 11 (nothing in `<out>`) unless they match.
- Parse `result.json` (`return_code`, `error_string`, `sliced_plates`) and capture untimestamped stderr lines as `details`.

**Outputs:**
Expand Down Expand Up @@ -218,13 +236,14 @@ All recipes use `brim_type = no_brim`. **Brims are opt-in** (`--set brim_type=ou
| sdcard | no SD card | |
| file_integrity | plate gcode md5 ≠ embedded .md5 | |
| sliced_for_printer | 3MF `printer_model` ≠ configured model | |
| single_filament | > 1 filament used | |
| single_filament | number of `--slot`s ≠ filaments in the plate | |
| nozzle_diameter | ≠ printer | |
| | | nozzle_type: material differs |
| bed_type | 3MF plate ≠ configured plate | |
| tray | slot empty, or type ≠ file type | filament_id differs (e.g. GFA01 vs GFA00) |
| filament_amount | remain_g < used_g × 1.15 + 5 | remaining unknown |
| nozzle_temp | outside filament range | |
| | tray to nozzle_temp run once per filament against its own slot; rows carry `"filament": N` on multi-filament jobs | |
| | | ai_protections: first_layer_inspector / spaghetti_detector / print_halt off |
| ftps_login | login + NLST failed | |

Expand All @@ -250,7 +269,9 @@ internal/errfmt/ exit codes, typed errors with hints
## 12. Safety rules (non-negotiable)

- `print send` requires `--confirm` and a PASS from preflight, re-run at send time. `--dry-run` never connects FTPS for writing and never publishes.
- `print pause|resume|stop` require `--confirm`.
- `print pause|resume|stop` and `filament load` require `--confirm`. `filament load` runs only while PAUSE or
IDLE/FINISH/FAILED. It refuses during a change, with a blocking HMS code, or on an empty slot. When paused, it also
refuses without an AMS tray in the toolhead, or with a different material than the job's.
- No command changes printer settings.
- The access code is never printed, never logged, and never written to config. `ffmpeg` errors are redacted.
- `--no-input` or a non-TTY stdin never prompts.
Expand Down
8 changes: 6 additions & 2 deletions docs/protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ September 2026).
- **Status:** `{"pushing":{"sequence_id":"0","command":"pushall"}}` requests the full status. X1 printers also push
about one partial `print.push_status` per second while printing; merge them. (verified)
- **Developer Mode:** `print.fun` is a hex bitfield. Bit `0x20000000` means signed commands are required, i.e. Developer Mode is **off**. (verified: `…1A30…` off, `…1830…` on)
- **Control:** `{"print":{"sequence_id":"<n>","command":"pause"|"resume"|"stop","param":""}}` at QoS 1. (not yet verified)
- **Control:** `{"print":{"sequence_id":"<n>","command":"pause"|"resume"|"stop","param":""}}` at QoS 1. (`resume` verified: PAUSE → RUNNING within seconds; `pause`/`stop` not yet verified)
- **AMS state:** `print.ams_status >> 8` is Bambu Studio's `AmsStatusMain`: `0x00` idle, `0x01` filament_change, `0x02` rfid_identifying, `0x03` assist (resting, filament loaded), `0x04` calibration, `0x07` cold_pull, `0x10` self_check, `0x20` debug. `ams.tray_now` is the tray in the toolhead (`255` none, `254` external spool). A G-code pause (`M400 U1`) reports `PAUSE` with `print_error` `0300_8013`. (verified)
- **Filament load:** `{"print":{"sequence_id":"<n>","command":"ams_change_filament","ams_id":A,"slot_id":S,"target":A*4+S,"curr_temp":T1,"tar_temp":T2}}`, as Studio's `command_ams_change_filament` sends it. Each temperature is the middle of that tray's `nozzle_temp_min`/`max` (Studio's default is 210). The printer cuts and unloads, loads the target and purges: `ams_status` goes `filament_change`, then `tray_now` becomes the target and `ams_status` returns to `assist`. (verified on an X1 Carbon during a pause, 2026-10-08: A4 → A1, then `resume` → RUNNING, and the print finished cleanly.)

## Start a job: `print.project_file` (verified)

Expand All @@ -23,7 +25,7 @@ September 2026).
```

- **Reply:** `{"print":{"command":"project_file","result":"SUCCESS",…}}`, and `gcode_state` goes PREPARE/RUNNING within seconds.
- **`ams_mapping`:** Bambu Studio's "v0" format (`SelectMachine.cpp::get_ams_mapping_result`). One entry per project filament; the value is the global tray id (`ams_index*4 + slot`, 0-based); `-1` = unused. `[3]` = first AMS, slot 4 (verified).
- **`ams_mapping`:** Bambu Studio's "v0" format (`SelectMachine.cpp::get_ams_mapping_result`). One entry per project filament; the value is the global tray id (`ams_index*4 + slot`, 0-based); `-1` = unused. `[3]` = first AMS, slot 4 (verified). A two-filament job sends `[3, 0]` for A4 then A1 (from Studio's source; not yet verified on a real print). Studio also sends `ams_mapping2` (`[{ams_id, slot_id}]`); the verified single-filament payload omits it.
- **Without Developer Mode**, the reply is a failure mentioning verification and HMS `0500_0500_0001_0007`. `bambu` maps that to exit 6.

## FTPS (upload)
Expand Down Expand Up @@ -63,3 +65,5 @@ Bambu Studio binds UDP 2021 exclusively while it runs. Broadcasts don't cross VL
- **Exit codes:** the shell sees `256 + return_code`. `result.json` holds `return_code`/`error_string`; untimestamped stderr lines hold the reason.
- **100% sparse infill** only accepts patterns that are also valid top-surface patterns (e.g. `zig-zag`, `monotonic`, `concentric`). Otherwise it fails with `-18`.
- **STEP input** fails with `-6`.
- **Layer-height colour change:** `--load-custom-gcodes c.json` with `{"mode":"MultiAsSingle","gcodes":[{"type":"ToolChange","print_z":Z,"extruder":2,"color":"#RRGGBB","extra":""}]}` (`CustomGCode::Info::from_json`). `print_z` is the first layer printed with the new filament (`IMSlider::add_code_as_tick`). The G-code then carries `M620 S1A` / `T1` / `M621 S1A` at that layer, and the AMS swaps without a pause. Studio drops a ToolChange silently when the mode doesn't fit (`ToolOrdering.cpp`), so read the `T<n>` lines back. `--filament-colour "#A;#B"` sizes the purge between filaments. (verified: 27-layer part, a single `T1` at layer 21 / Z 4.2 for a change after Z 4.0)
- **Per-filament settings:** temperatures that vary by nozzle variant hold one entry per (filament, variant) pair in `project_settings.config`. Look them up through `filament_self_index` and `filament_extruder_variant`; plain indexing reads the wrong filament in a two-filament project.
Loading
Loading