mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
An X2D with two AMS 2 Pro, one per hotend, showed a K value on every slot of the first and nothing on any slot of the second. Configure Slot was worse than blank there: the picker offered no matching profile, the slot read as though nothing were bound, and choosing one changed nothing the user could see. Both symptoms are one rule. A calibration index can mean two different profiles on a dual-nozzle machine -- on the maintainer's H2C, index 16 is the left hotend's black PLA at K=0.018 and 15 is the right's at K=0.020 -- so the index is resolved against the slot's own hotend, and a miss shows nothing rather than the other nozzle's number. That is right whenever the printer files its calibrations per hotend. This one files them per filament: the second AMS's slots point at the same entries as the first, every entry tagged with one extruder, and requiring a match found nothing at all. The hotend now has to appear in the table the printer actually sent before it is used to narrow anything. Where it does not, the index stands on its own, which is what BambuStudio does for this same card -- AMSItem.cpp resolves it through get_pa_k_n_value_by_cali_idx, matching cali_idx and nothing else. Where it does, nothing changes: the H2C case still blanks rather than borrowing, and the other hotend's profiles stay reachable under Other K profiles. The relaxed path still refuses an answer when the candidates disagree on a value. The premise that the table is always numbered per nozzle had been written into three comments and two layers of code; it is corrected where it appears. Alongside it, in the same picker: the K-profile options rendered the hotend suffix twice in the matching group and three times under Other, so every option on a dual-nozzle printer read "... . Left . Left".
80 lines
3.5 KiB
Python
80 lines
3.5 KiB
Python
"""Which nozzle an AMS slot feeds, with or without a Filament Track Switch.
|
|
|
|
K-profiles are per-nozzle: a calibration run belongs to the hotend it ran on,
|
|
and ``cali_idx: 16`` can name a different profile on each. (It need not — one
|
|
profile can also be what both extruders' slots point at, which is why the K
|
|
lookup in ``kprofile_lookup`` treats the extruder as a preference rather than a
|
|
filter — but the routing question below is the same either way.) Without a
|
|
switch the answer is unambiguous, because each AMS is wired to one extruder and
|
|
says so in its ``info`` bits. With a switch installed every AMS reports 0xE
|
|
instead and is bound to a switch *inlet*, so the answer has to come from the
|
|
inlet binding.
|
|
|
|
Every caller that resolves a slot to an extruder should go through
|
|
``slot_extruder`` here. Three separate copies of that logic used to end in
|
|
``else 0``, which on a switch machine silently filed every profile under the
|
|
right-hand nozzle regardless of where the slot actually was.
|
|
|
|
Kept as a leaf module with no imports of its own so the routes, the MQTT layer
|
|
and the scheduler can all share one answer.
|
|
"""
|
|
|
|
# Which extruder each switch outlet terminates at. Measured on the maintainer's
|
|
# H2C, 2026-08-16: Out-A is the left hotend, Out-B is the right one.
|
|
#
|
|
# We would rather read this than assert it, but it is not in the telemetry:
|
|
# ``fila_switch.out`` reported ``[1, 1]`` unchanged across a 90-second capture,
|
|
# i.e. both outlets claiming the same extruder, which cannot describe real
|
|
# wiring. Whatever that field means, it is not outlet-to-nozzle.
|
|
#
|
|
# The inlet-to-outlet pairing then comes from the switch's un-crossed rest
|
|
# position: In-A -> Out-A, In-B -> Out-B. The switch does cross the two during a
|
|
# filament change, so this describes where a slot sits between prints, which is
|
|
# what a manual "configure this slot" task needs. It is deliberately one table
|
|
# to change if a machine turns up with its outlet tubes swapped.
|
|
FTS_INLET_EXTRUDER: dict[str, int] = {
|
|
"A": 1, # left / deputy
|
|
"B": 0, # right / main
|
|
}
|
|
|
|
|
|
def extruder_for_inlet(inlet: str | None) -> int | None:
|
|
"""Extruder fed by switch inlet ``"A"`` or ``"B"``; None for anything else."""
|
|
if not inlet:
|
|
return None
|
|
return FTS_INLET_EXTRUDER.get(inlet.upper())
|
|
|
|
|
|
def slot_extruder(
|
|
ams_id: int,
|
|
tray_id: int,
|
|
ams_extruder_map: dict | None,
|
|
ams_switch_inlet: dict | None = None,
|
|
) -> int | None:
|
|
"""Resolve one AMS slot to the extruder it feeds, or None if unknowable.
|
|
|
|
Returns None rather than guessing. A single-nozzle printer has no map and no
|
|
switch, and there the caller's own default of extruder 0 is correct — but on
|
|
a dual-nozzle machine "I don't know" and "the right-hand nozzle" are very
|
|
different answers, and conflating them is what bound a left-nozzle K-profile
|
|
to a slot sitting on the right.
|
|
|
|
``ams_id`` 255 is the external spool holder, where the tray id names the
|
|
side directly: tray 0 is Ext-L (extruder 1) and tray 1 is Ext-R (extruder 0).
|
|
"""
|
|
if ams_id == 255:
|
|
return 1 - tray_id if tray_id in (0, 1) else None
|
|
|
|
# A real extruder id always wins. BambuStudio treats a non-0xE value as
|
|
# authoritative too, so an AMS wired straight to one nozzle keeps that
|
|
# binding even on a machine that has a switch fitted for its other units.
|
|
if ams_extruder_map:
|
|
mapped = ams_extruder_map.get(str(ams_id))
|
|
if mapped is not None:
|
|
return int(mapped)
|
|
|
|
if ams_switch_inlet:
|
|
return extruder_for_inlet(ams_switch_inlet.get(str(ams_id)))
|
|
|
|
return None
|