bambuddy/backend/app/utils
maziggy ec26cba927 Resolve the H2C rack nozzle at dispatch instead of letting firmware pick (#2800)
An H2C ran its startup clean and bed levelling on one hotend, switched,
and then printed several millimetres above the plate. The same job from
Bambu Studio was fine.

The H2C is the only model that mounts its nozzle from a rack of six, and
a print command names that nozzle by physical rack position -- the
firmware reports those as 16 to 21 -- not by the extruder index, 0 or 1,
every other dual-nozzle printer uses. Bambuddy only ever had a rack
position when a job arrived through the Virtual Printer, which captures
Bambu Studio's pick and replays it (#1780). Anything queued from the
library, an archive, the webhook or a slicer pipeline carried none, so
the field was omitted and the firmware chose -- and its choice need not
match what the file was sliced for.

The scheduler now derives the per-slot extruder assignment from the file
it is about to send, and the MQTT layer resolves it against the rack
position the printer reports live. Both are needed: the file knows which
side a slot prints from, only the printer knows which hotend is in the
carriage, and it can be swapped from the touchscreen between queueing a
job and printing it.

Derived at dispatch rather than at creation because that is the first
point knowing both the real printer and the real file -- an item can be
created unassigned, reassigned later, or have its file swapped for a
G-code-injected copy. One call therefore covers the print dialog, bulk
library adds, the webhook and pipeline runs, and no column is needed.

extract_nozzle_mapping_from_3mf is deliberately untouched. Its output
feeds the AMS matcher, where nozzle_id is compared against a tray's
extruder_id as a hard filter, and physical_extruder_map is what makes
that comparison correct -- on an H2D it is [1, 0] and flips the two.
Dropping the translation to suit the rack would send every dual-nozzle
AMS match to the wrong extruder. The dense per-slot form is a separate
function reusing the same output.

Nothing here can fail a dispatch. The command is built and published with
no exception handler above it, and the queue item is already committed as
printing by then, so a bad input has to degrade to "firmware picks"
rather than wedge the item. resolve_rack_nozzle_mapping validates every
input and raises nothing; an unresolvable mapping, an unparseable value
or an unknown rack position all omit the field, which is the behaviour
that existed before. Slot IDs are bounded before the dense list is built:
they come from the file, and one declaring filament id="50000000" would
otherwise allocate a fifty-million-entry list on the dispatch path.

Two things are not guessed. A job printing only from the fixed hotend is
still left to the firmware, because that nozzle's physical ID is not
confirmed by a known-good capture. And the rack is taken to feed extruder
0 from a single hardware observation -- if that is flipped, a one-sided
job matches nothing and falls back to the old behaviour, so only a job
using both nozzles at once could be harmed, which is what a second
capture needs to confirm.

Confined to the H2C throughout. Building the print command for 21 model
spellings with and without the new argument changes exactly three of them
-- H2C, O1C and O1C2. The other 18, including H2D and X2D, are identical.

Reported by @tru3l3gend, who diagnosed it on real hardware against a
working Bambu Studio dispatch, established the rack ID range and supplied
a patch.
2026-08-10 08:54:44 +02:00
..
color_utils.py fix(inventory): fix usage tracking for remapped AMS slots and slicer prints 2026-02-16 15:46:32 +01:00
filament_ids.py Feature/spoolman inventory UI (#1241) 2026-05-08 11:52:42 +02:00
filename.py fix(dispatch): align SD cleanup with upload path so doubled-extension library rows don't leave ghost prints (#1542) 2026-05-28 08:40:43 +02:00
http.py fix(http): RFC 6266-encode Content-Disposition so non-ASCII filenames don't crash response (issue #1245) 2026-05-08 14:06:40 +02:00
local_time.py fix(smart-plugs): read a REST plug's lifetime counter, and derive Today/Yesterday from it (issue #2539) 2026-07-11 14:22:31 +02:00
printer_models.py Resolve the H2C rack nozzle at dispatch instead of letting firmware pick (#2800) 2026-08-10 08:54:44 +02:00
safe_path.py security: harden path-traversal class across routes + services; fifth CI backstop 2026-06-02 12:12:54 +02:00
tag_normalization.py [Feature] Spoolbuddy Fixes and Improvements (#787) 2026-03-24 11:56:33 +01:00
threemf_tools.py Resolve the H2C rack nozzle at dispatch instead of letting firmware pick (#2800) 2026-08-10 08:54:44 +02:00
tray_split.py fix(spoolman): split mid-print usage across AMS backup switch (#1793) 2026-07-01 09:50:30 +02:00