mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 07:33:18 +02:00
Show which Filament Track Switch inlet each AMS feeds
With a switch fitted, an AMS is not wired to a nozzle any more. It is plumbed into one of the switch's two inlets and reaches both nozzles through it, so every unit reports its extruder as "not fixed" (0xE) and ams_extruder_map comes back empty on these machines. The printer card had nothing to fall back on but the AMS unit number, so AMS-A was badged R and AMS-B was badged L purely because their unit ids are 0 and 1, a third unit got no badge at all, and every one of those labels was wrong. The SpoolBuddy assign modal had the same fallback in a worse form, mapping anything that was not extruder 1 to R. The binding turned out to need no new telemetry. BambuStudio reads it out of bits 24-27 of the same AMS info string we already parse for the AMS type and the extruder id -- 0 is In-B, 1 is In-A -- and it is only meaningful when a switch is installed, because without one 0xE really does mean an uninitialised unit and those bits carry nothing. That gates the read, which in turn forced the switch block to be parsed before the AMS block: _handle_ams_data runs early in _process_message and _update_state only much later, so the binding was lost on every frame that carried both. _parse_fila_switch is split out and called first, and left in _update_state as well so that stays a complete absorb step. The badge keeps L and R rather than A and B, because the lettering is familiar and matches the physical layout. It is a different colour from the plain nozzle badge, and its tooltip names the inlet in full, since the letter is the inlet's position and not a claim about which nozzle that AMS feeds -- the switch can route either inlet to either outlet. An AMS still reporting a real extruder id keeps its ordinary badge, which BambuStudio also treats as authoritative over any switch binding, and a switch that has been fitted but not yet set up on the printer shows nothing rather than a guess. The print dialog's slot dropdown gets the same label. It replaces a left/right hint that never once rendered: ftsExtruderForSlot compared snow-encoded in[] values against global tray ids and could not match. Decoding it correctly would not have saved it -- the firmware reports which slot sits in each inlet and which nozzle each outlet feeds, but never which inlet is currently paired with which outlet, so no per-slot nozzle can be derived. That function is gone rather than fixed. The dialog also points out when every filament a print needs sits behind one inlet. Bambu's own guidance is that this is legal but slow: a change between two filaments on the same inlet retracts the outgoing spool all the way back to its AMS before the next can be fed up the shared tube, where a change across the two inlets only retracts as far as the switch. All on one inlet means every change in the job takes the slow path, and moving a single spool fixes it. So it advises, it does not block. Both views update live. Two things were stopping that. fila_switch and ams_switch_inlet were absent from printer_state_to_dict, and the frontend shallow-merges each WebSocket push over its cached status, so a field the push omits keeps whatever the last full fetch left behind. And the broadcast dedup key had no term for either, so "Join IN-B" on the printer screen moved nothing: the binding is not in the tray component of that key, and it is not in the AMS change-hash either, which covers tray fields only and must stay that way because it drives Spoolman sync. Assigning an AMS to an inlet remains printer-side. BambuStudio can read the binding and has no command to write it -- its switch class is parse and getters only, and the recommended-arrangement popup draws and publishes nothing -- so there is no wire format for us to copy. Adding the two fields to PrinterState broke four test modules whose SimpleNamespace stubs predate them. The stubs are fixed rather than the production reads made defensive: the real dataclass always carries both, and a getattr in the dedup key would silently stop tracking the field if it were ever renamed.
This commit is contained in:
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Circle, Check, AlertTriangle, RefreshCw, ChevronDown, ChevronUp, Palette } from 'lucide-react';
|
||||
import { api } from '../../api/client';
|
||||
import { useFilamentMapping } from '../../hooks/useFilamentMapping';
|
||||
import { getGlobalTrayId, effectivePreferLowest } from '../../utils/amsHelpers';
|
||||
import { getGlobalTrayId, effectivePreferLowest, FTS_INLET_SIDE } from '../../utils/amsHelpers';
|
||||
import { getColorName } from '../../utils/colors';
|
||||
import { useFilamentLabels } from './useFilamentLabels';
|
||||
import { autoAssignRackPositions, rackOptionsForGroup } from '../../utils/nozzleRack';
|
||||
@@ -234,16 +234,39 @@ export function FilamentMapping({
|
||||
|
||||
// Filament Track Switch: when installed, AMS-to-extruder mapping is dynamic
|
||||
// (any slot can be routed to either extruder), so the per-nozzle dropdown
|
||||
// filter is suppressed. fila_switch.in_slots[track] = currently fed slot,
|
||||
// fila_switch.out_extruders[track] = extruder that track terminates at. See #1162.
|
||||
// filter is suppressed. See #1162.
|
||||
//
|
||||
// What a slot CAN be labelled with is the switch inlet its AMS is plumbed
|
||||
// into (ams_switch_inlet, from AMS info bits 24-27). That is the stable
|
||||
// relationship the printer's own "Manual AMS Setup" screen sets. The live
|
||||
// inlet-to-outlet route is deliberately not shown: the firmware never reports
|
||||
// which inlet is currently paired with which outlet, so any left/right label
|
||||
// on a slot would be a guess.
|
||||
const ftsInstalled = printerStatus?.fila_switch?.installed === true;
|
||||
const ftsExtruderForSlot = (globalTrayId: number): number | null => {
|
||||
const fs = printerStatus?.fila_switch;
|
||||
if (!fs?.installed) return null;
|
||||
const track = fs.in_slots.indexOf(globalTrayId);
|
||||
if (track < 0) return null;
|
||||
return fs.out_extruders[track] ?? null;
|
||||
};
|
||||
const amsSwitchInlet = printerStatus?.ams_switch_inlet;
|
||||
const ftsInletForAms = (amsId: number): 'A' | 'B' | null =>
|
||||
(ftsInstalled && amsSwitchInlet?.[String(amsId)]) || null;
|
||||
|
||||
// Every filament for this print sitting behind one inlet is the case worth
|
||||
// flagging. Bambu's own guidance: a change between two filaments on the same
|
||||
// inlet has to retract the old one all the way back to its AMS before the new
|
||||
// one can be fed through the shared tube, where a change across the two
|
||||
// inlets only retracts as far as the switch. All-on-one-inlet means every
|
||||
// single change in the job takes the slow path.
|
||||
const sameInletWarning = useMemo(() => {
|
||||
if (!ftsInstalled) return null;
|
||||
const inlets = new Set<string>();
|
||||
for (const item of filamentComparison) {
|
||||
if (!item.loaded || item.loaded.isExternal) return null;
|
||||
const inlet = ftsInletForAms(item.loaded.amsId);
|
||||
if (!inlet) return null;
|
||||
inlets.add(inlet);
|
||||
}
|
||||
if (filamentComparison.length < 2 || inlets.size !== 1) return null;
|
||||
return [...inlets][0];
|
||||
// ftsInletForAms is a stable closure over the two values already listed.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ftsInstalled, amsSwitchInlet, filamentComparison]);
|
||||
|
||||
// Don't render if no filament requirements
|
||||
if (!hasFilamentReqs) {
|
||||
@@ -345,6 +368,12 @@ export function FilamentMapping({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{sameInletWarning && (
|
||||
<div className="flex items-start gap-1.5 rounded border border-yellow-500/40 bg-yellow-500/10 px-2 py-1.5 text-xs text-yellow-700 dark:text-yellow-400">
|
||||
<AlertTriangle className="w-3 h-3 mt-0.5 shrink-0" />
|
||||
<span>{t('printModal.ftsSameInletHint', { inlet: sameInletWarning })}</span>
|
||||
</div>
|
||||
)}
|
||||
{filamentComparison.map((item, idx) => {
|
||||
// #1717: surface the same per-slot force-color-match checkbox here
|
||||
// that FilamentOverride exposes for model-mode dispatch. The
|
||||
@@ -460,16 +489,15 @@ export function FilamentMapping({
|
||||
defaultValue: ` - ${remainingWeight}g left`,
|
||||
})
|
||||
: '';
|
||||
// FTS routing badge: if this slot is currently fed into an FTS
|
||||
// track, show the destination extruder. Idle (not-loaded) slots
|
||||
// get no badge — they can be routed to either extruder on demand.
|
||||
const ftsTargetExtruder = ftsInstalled
|
||||
? ftsExtruderForSlot(f.globalTrayId)
|
||||
: null;
|
||||
const ftsBadge =
|
||||
ftsTargetExtruder == null
|
||||
? ''
|
||||
: ` [${ftsTargetExtruder === 1 ? t('printModal.leftNozzle') : t('printModal.rightNozzle')}]`;
|
||||
// FTS badge: which switch inlet this slot's AMS feeds. Not a
|
||||
// nozzle — the slot reaches both through the switch — but it
|
||||
// is what decides whether a change to the next filament is
|
||||
// the fast cross-inlet one or the slow same-inlet one.
|
||||
const ftsInlet = ftsInletForAms(f.amsId);
|
||||
// Same L/R lettering the printer card uses for inlets, so the
|
||||
// two views agree. Not translated: L and R are the letters on
|
||||
// the machine.
|
||||
const ftsBadge = ftsInlet == null ? '' : ` [${FTS_INLET_SIDE[ftsInlet]}]`;
|
||||
return (
|
||||
<option key={f.globalTrayId} value={f.globalTrayId} className="bg-bambu-dark text-white">
|
||||
{f.label}: {f.traySubBrands || f.type} ({f.colorName}){remainingLabel}{ftsBadge}
|
||||
|
||||
Reference in New Issue
Block a user