Make the filament source explicit, and print from the AMS

`start_uploaded_file` hardcoded `use_ams: false` / `ams_mapping: ""`, which
silently overrode whatever the slicer had chosen. On a P1P with an AMS and a
project sliced for it, that is not a missing feature - it points the print at
the external spool. Confirmed on 2026-10-11: the print started, heated to
250C, reached 1%, then the printer paused itself with `print_error: 50364420`
and `tray_now: 255`. Nothing in the command's reply said so; `result` was
`success`, because the command had been accepted.

So `FilamentSource` is now a required argument with no default, because there
is no safe default - picking wrong draws the wrong filament or none. The
`ExternalSpool` variant sends the empty *string* the schema's trailing
`oneOf` demands, and `ams` takes slots per project filament index and pads to
every tray the printer has, per the padding rule in
`schemas/bambulab/v1/common/ams/ams_mapping.schema.json`. It refuses an empty
mapping (padding `[]` would mean `use_ams: true` with nothing mapped), one
longer than the printer has trays, and any negative that is not -1.

Confirmed live with the real mapping: `ams_mapping: [3,-1,-1,-1,-1]`,
`use_ams: true` took the printer to RUNNING, `tray_now` became 3 - the slot
the project was sliced for - and it ran past 1% to 13% with `print_error: 0`,
where the external-spool attempt had paused.

Finding the mapping needed reading the project back off the card, so FTPS
gains `download`/`download_file` and the probe a `fetch` step. The archive's
`Metadata/slice_info.config` lists each plate's filaments with
`tray_info_idx` and colour - here one PETG GFG99 at `#2850E0`, matching AMS
slot 3's `2850E0FF` - and `Metadata/plate_1.gcode` is present, which also
confirms the hardcoded `param`.

`upload_project_file` is left on the external spool and is the one remaining
place that can pick wrong, because it is vendor-generic and an AMS mapping is
a Bambu concept. It is unreachable - there is no start-print command on the
uplink - and the fix is for it to derive the mapping from the `.3mf` it
already holds, which needs a zip reader and so a dependency decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-11 22:35:51 +00:00
co-authored by Claude Opus 5
parent 79df3e7153
commit 7d68e8e069
4 changed files with 278 additions and 11 deletions
+36
View File
@@ -116,6 +116,42 @@ pub fn upload(
Ok(written)
}
/// Blocking: reads one file off the printer's SD card.
///
/// Read-only. The reason this exists: a `.3mf` already on the card is the
/// only record of which filament each of its plates was sliced for, and
/// `ams_mapping` cannot be built without that - see
/// `schemas/bambulab/v1/common/ams/ams_mapping.schema.json`. Printing a
/// project with the wrong mapping is not a cosmetic error; it draws the
/// wrong filament, or none.
pub fn download(
host: &str,
access_code: &str,
connector: native_tls::TlsConnector,
path: &str,
) -> anyhow::Result<Vec<u8>> {
let mut ftp = NativeTlsFtpStream::connect_secure_implicit(
(host, FTPS_PORT),
NativeTlsConnector::from(connector),
host,
)?;
ftp.login(FTPS_USER, access_code)?;
// A `.3mf` is a zip archive; letting the server treat it as text is free
// corruption, exactly as on the way up.
ftp.transfer_type(FileType::Binary)?;
// `retr_as_buffer` rather than `retr_as_stream`: suppaftp 12's
// `TransferStream` finalizes itself on drop, and the files being read
// here are project archives of a few hundred KB, so there is nothing to
// gain from streaming them.
let bytes = ftp.retr_as_buffer(path)?.into_inner();
let _ = ftp.quit();
Ok(bytes)
}
/// Blocking: lists `cache/` on the printer.
///
/// Read-only, and the counterpart to `upload`. The previous version of this