mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 07:33:18 +02:00
Name an AMS slot after the spool assigned to it
The print dialog described every slot from the printer's own telemetry, and
a printer cannot describe a spool it did not sell: a tray record carries no
brand field, tray_sub_brands is left empty for anything that is not a Bambu
spool, and the colour arrives as a bare hex the client resolves against
Bambu's own colour catalogue. A Devil Design PLA Basic Orange assigned in
Bambuddy therefore read as "PLA (Sunflower Yellow)" -- Bambu sell a
Sunflower Yellow at the same FEC600 -- while the printer card, which reads
the assignment, named it correctly. Two views of one slot, disagreeing.
GET /printers/{id}/inventory-remain now carries each bound slot's brand,
material, subtype, colour name and hex alongside the pooling key it already
sent, and the dialog prefers that over telemetry. The fallback is per field,
not all or nothing, so a spool with no stored colour name still gets the
catalogue lookup it had before while its brand and subtype come from the
binding. Resolved server-side because the identity rule differs per
inventory mode -- brand is a column in internal mode and a nested vendor in
Spoolman's, where the subtype is the filament name with its material prefix
stripped and the colour name has a three-step read order Spoolman has no
field for. Spoolman's synthesised colour name, which falls back to the
subtype, is withheld rather than rendered as "PLA Basic (Basic)".
Matching is deliberately untouched and still runs on the printer's
telemetry. The auto-assignment, the colour-mismatch test and the mapping
that actually gets dispatched all read type, colour hex and tray_info_idx,
so renaming a slot cannot make the panel and the dispatcher draw different
conclusions from it. The payload is re-read on every open of the dialog: it
names the slots now, and a spool assigned moments earlier would otherwise
keep its old name for the rest of the thirty-second stale window. Done at
the two readers rather than by invalidating the key from each of the
eighteen places a binding or a spool can change, half of which are internal
paths and half Spoolman ones -- covering some would make freshness depend on
which mode you run.
Two hardening fixes fall out of putting a mapper on this path.
build_slot_materials runs before every queue start through
compute_deficit_for_queue_item, and _map_spoolman_spool walks a dozen nested
fields off the wire, any of which arriving as the wrong type raises
AttributeError rather than ValueError. Naming a slot must never cost a
dispatch, so that call fails soft to no name. The same inputs also reached
_material_identity_spoolman and _normalize_color_for_id, which have always
been on this path and would fail a queue start on a Spoolman record whose
filament is not a dict or whose color_hex is a number; both now read those
as "nothing to pool with". Behaviour for well-formed input is unchanged --
the guards only intercept types that previously raised -- so no pooling key
moves and AMS Filament Backup is untouched.
This commit is contained in:
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Circle, Check, AlertTriangle, RefreshCw, ChevronDown, ChevronUp, Palette } from 'lucide-react';
|
||||
import { api } from '../../api/client';
|
||||
import type { SlotSpoolIdentity } from '../../api/client';
|
||||
import { useFilamentMapping } from '../../hooks/useFilamentMapping';
|
||||
import { getGlobalTrayId, effectivePreferLowest, FTS_INLET_SIDE } from '../../utils/amsHelpers';
|
||||
import { disambiguateColorNames, getColorName } from '../../utils/colors';
|
||||
@@ -112,6 +113,14 @@ export function FilamentMapping({
|
||||
queryFn: () => api.getInventoryRemain(printerId),
|
||||
enabled: !!printerId,
|
||||
staleTime: 30 * 1000,
|
||||
// Fresh on every open, cached while open. This payload now names the
|
||||
// slots, and a spool assigned moments ago would otherwise keep its old
|
||||
// name for the rest of the stale window. Doing it here rather than
|
||||
// invalidating the key from each of the eighteen places a binding or a
|
||||
// spool can change: half of those are internal-inventory paths and half
|
||||
// are Spoolman ones, and covering some of them would make freshness
|
||||
// depend on which inventory mode you run.
|
||||
refetchOnMount: 'always',
|
||||
});
|
||||
const inventoryByTrayId = useMemo(() => {
|
||||
if (!inventoryRemain?.inventory_remain_g) return undefined;
|
||||
@@ -122,13 +131,34 @@ export function FilamentMapping({
|
||||
});
|
||||
return map;
|
||||
}, [inventoryRemain]);
|
||||
// The other half of the same payload: what Bambuddy has bound to each slot,
|
||||
// so a slot reads as the spool the operator assigned rather than as whatever
|
||||
// the printer can say about it. A third-party spool reports no sub-brand at
|
||||
// all and its colour hex resolves against Bambu's catalogue, so without this
|
||||
// the dialog named slots differently from the printer card.
|
||||
const slotSpools = useMemo(() => {
|
||||
const slots = inventoryRemain?.slot_materials;
|
||||
if (!slots?.length) return undefined;
|
||||
const map = new Map<number, SlotSpoolIdentity>();
|
||||
slots.forEach((slot) => {
|
||||
if (slot.spool) map.set(slot.global_tray_id, slot.spool);
|
||||
});
|
||||
return map.size > 0 ? map : undefined;
|
||||
}, [inventoryRemain]);
|
||||
const gatedPreferLowest = effectivePreferLowest(
|
||||
settings?.prefer_lowest_filament,
|
||||
printerStatus?.ams_filament_backup,
|
||||
);
|
||||
|
||||
const { loadedFilaments, filamentComparison, hasTypeMismatch, hasColorMismatch } =
|
||||
useFilamentMapping(filamentReqs, printerStatus, manualMappings, gatedPreferLowest, inventoryByTrayId);
|
||||
useFilamentMapping(
|
||||
filamentReqs,
|
||||
printerStatus,
|
||||
manualMappings,
|
||||
gatedPreferLowest,
|
||||
inventoryByTrayId,
|
||||
slotSpools,
|
||||
);
|
||||
|
||||
// Per-slot sub-brand + material-disambiguated colour labels (#1718). Same
|
||||
// shared hook the model-mode FilamentOverride uses so both panels render
|
||||
@@ -508,7 +538,7 @@ export function FilamentMapping({
|
||||
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}
|
||||
{f.label}: {f.spoolName || f.traySubBrands || f.type} ({f.colorName}){remainingLabel}{ftsBadge}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -352,6 +352,10 @@ export function PrintModal({
|
||||
queryKey: ['printer-inventory-remain', printerId],
|
||||
queryFn: () => api.getInventoryRemain(printerId),
|
||||
staleTime: 30 * 1000,
|
||||
// Same key, same reason as FilamentMapping's copy — see the note there.
|
||||
// Concurrent mounts dedupe, so opening the dialog costs one fetch per
|
||||
// printer however many plate panels are on screen.
|
||||
refetchOnMount: 'always',
|
||||
enabled: selectedPrinters.length > 0,
|
||||
})),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user