Refuse a truncated project, and drive the whole path from the probe
`upload_project_file` already existed - as a `GenericPrinter` trait method, which is why a grep for `pub async fn` missed it and this commit nearly reimplemented it. It logged `written` and `byte_count` next to each other and never compared them, so a short write was something you could only notice by reading the logs afterwards, and the next line printed the truncated file anyway. A `.3mf` is a zip: cut the end off and the central directory goes with it, so the printer gets an archive it cannot open. That is also the failure a slow transfer invites, and this transfer is slow - 4.96 MB took 31.3s, so a 62 MB project is minutes of exposure. `print-project <local> [name]` runs that method against a real printer, so the harness tests the production entry point rather than the three pieces in sequence. It deliberately takes no `--ams`: the whole point is that the mapping comes out of the archive. Gated like `start`, on a terminal state and `--yes-start-a-real-print`. Confirmed end to end on the P1P (2026-10-15, with the owner's go-ahead), with the same project the 2026-10-11 run used: the derivation read one PETG/GFG99/ `#2850E0` filament out of `Metadata/slice_info.config` and produced `mapping=[3]`, padded to five trays; 297,227 bytes went up with an exact byte count; `IDLE -> PREPARE -> RUNNING`. The AMS on that machine has slot 2 in black and slot 3 in blue, same product and same `tray_info_idx`, so picking slot 3 is the colour tie-break doing real work rather than a one-candidate match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
//! at all? A clean rejection naming the file means the
|
||||
//! shape is right and only the target was wrong. A
|
||||
//! parse error, or silence, means the shape is wrong.
|
||||
//! - `print-project <local> [name]`
|
||||
//! **starts a real print.** The whole production path in
|
||||
//! one call - derive the AMS mapping from the archive,
|
||||
//! upload, start - via
|
||||
//! `GenericPrinter::upload_project_file`. Gated on a
|
||||
//! terminal state and `--yes-start-a-real-print`. Takes
|
||||
//! no `--ams`: the point is that it reads the mapping
|
||||
//! itself.
|
||||
//! - `upload <local> [name]`
|
||||
//! **inert.** FTPS-transfers a local file onto the card
|
||||
//! and stops - nothing prints until told to. Reports
|
||||
@@ -294,6 +302,52 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// `print-project <local> [name]` is the whole path in one call:
|
||||
// derive the filament source from the archive, upload, start. Calls
|
||||
// `GenericPrinter::upload_project_file`, so it tests the production
|
||||
// entry point rather than the three pieces in sequence.
|
||||
"print-project" => {
|
||||
let Some(local) = args.get(3) else {
|
||||
anyhow::bail!("`print-project` needs a local .3mf path");
|
||||
};
|
||||
const READY_TO_START: [&str; 3] = ["IDLE", "FINISH", "FAILED"];
|
||||
if !READY_TO_START.contains(&state.as_str()) {
|
||||
anyhow::bail!(
|
||||
"printer reports '{state}', which is not one of {READY_TO_START:?} - \
|
||||
refusing to start a print on a machine that may already be working"
|
||||
);
|
||||
}
|
||||
if !confirmed {
|
||||
anyhow::bail!(
|
||||
"refusing to start a real print without --yes-start-a-real-print.\n\
|
||||
This uploads a project and commits the machine to it. Be at the printer."
|
||||
);
|
||||
}
|
||||
let data = std::fs::read(local)?;
|
||||
let name = args
|
||||
.get(4)
|
||||
.filter(|a| !a.starts_with("--"))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("continuum-probe-{}", file_stem(local)));
|
||||
println!("\n upload_project_file({name}, {} bytes):", data.len());
|
||||
println!(" no --ams flag: the mapping comes out of the archive, not this harness");
|
||||
let started = std::time::Instant::now();
|
||||
match bambu.upload_project_file(name.clone(), data).await {
|
||||
Ok(()) => println!(" accepted after {:?} - watching", started.elapsed()),
|
||||
Err(err) => {
|
||||
// The interesting failures are here: an unmappable
|
||||
// project should fail *before* the upload, leaving
|
||||
// nothing on the card.
|
||||
println!(" FAILED after {:?}: {err}", started.elapsed());
|
||||
println!(" if that was the mapping, the card should be untouched - check with `status`");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
watch(bambu, state).await;
|
||||
println!("\n the file is still on the card as {name} - `delete` it when done");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// `upload <local> [name]` puts a file on the card and stops there.
|
||||
// Inert: a file on the SD card does nothing until something is told
|
||||
// to print it. The only proven transfer before this was 102 bytes,
|
||||
|
||||
@@ -1348,6 +1348,22 @@ impl GenericPrinter for BambuPrinter {
|
||||
"uploaded a project file to the SD card root"
|
||||
);
|
||||
|
||||
// These two were logged next to each other and never compared, which
|
||||
// made a short write something you could only notice by reading the
|
||||
// logs afterwards - and the next line would have printed the
|
||||
// truncated file anyway. A `.3mf` is a zip: cut the end off and the
|
||||
// central directory goes with it, so the printer gets an archive it
|
||||
// cannot open rather than a partial model. It is also the failure a
|
||||
// slow transfer invites, and this one is slow: 4.96 MB took 31.3s on
|
||||
// a P1P (2026-10-15), so a 62 MB project is minutes of exposure.
|
||||
if written != byte_count as u64 {
|
||||
return Err(PrinterError(format!(
|
||||
"{}: uploaded {written} bytes of {byte_count} - refusing to print a \
|
||||
truncated project",
|
||||
self.base.name
|
||||
)));
|
||||
}
|
||||
|
||||
self.start_uploaded_file(&file_name, &filament).await
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user