mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Fix "Clear Plate" button shown on printers without matching filament color (#486)
When a print was queued to "any printer" with a filament color override (e.g., white PETG), the "Clear Plate & Start Next" button appeared on printers that had the right type but wrong color. The backend scheduler already rejected color mismatches, but the frontend only checked filament types. Now passes loaded type+color pairs from AMS status to the widget and filters against filament_overrides, mirroring the backend's _count_override_color_matches() logic.
This commit is contained in:
@@ -363,4 +363,169 @@ describe('PrinterQueueWidget - Clear Plate', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filament override color filtering', () => {
|
||||
const whitePetgOverrideItem = [
|
||||
{
|
||||
id: 20,
|
||||
printer_id: null,
|
||||
archive_id: 20,
|
||||
position: 1,
|
||||
status: 'pending',
|
||||
archive_name: 'White PETG Print',
|
||||
printer_name: null,
|
||||
print_time_seconds: 3600,
|
||||
scheduled_time: null,
|
||||
required_filament_types: ['PETG'],
|
||||
filament_overrides: [{ slot_id: 1, type: 'PETG', color: '#FFFFFF' }],
|
||||
},
|
||||
];
|
||||
|
||||
it('hides widget when override color does not match loaded filaments', async () => {
|
||||
server.use(
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json(whitePetgOverrideItem))
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilamentTypes={new Set(['PETG'])}
|
||||
loadedFilaments={new Set(['PETG:0000ff'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.querySelector('button')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.queryByText('White PETG Print')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows widget when override color matches loaded filaments', async () => {
|
||||
server.use(
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json(whitePetgOverrideItem))
|
||||
);
|
||||
|
||||
render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilamentTypes={new Set(['PETG'])}
|
||||
loadedFilaments={new Set(['PETG:ffffff'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('White PETG Print')).toBeInTheDocument();
|
||||
expect(screen.getByText('Clear Plate & Start Next')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('normalizes override color format (strips # and lowercases)', async () => {
|
||||
const upperCaseColorItem = [
|
||||
{
|
||||
id: 21,
|
||||
printer_id: null,
|
||||
archive_id: 21,
|
||||
position: 1,
|
||||
status: 'pending',
|
||||
archive_name: 'Red PLA Print',
|
||||
printer_name: null,
|
||||
print_time_seconds: 3600,
|
||||
scheduled_time: null,
|
||||
required_filament_types: ['PLA'],
|
||||
filament_overrides: [{ slot_id: 1, type: 'PLA', color: '#FF0000' }],
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json(upperCaseColorItem))
|
||||
);
|
||||
|
||||
render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilamentTypes={new Set(['PLA'])}
|
||||
loadedFilaments={new Set(['PLA:ff0000'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Red PLA Print')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows widget when no loadedFilaments prop is provided (no color filtering)', async () => {
|
||||
server.use(
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json(whitePetgOverrideItem))
|
||||
);
|
||||
|
||||
render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilamentTypes={new Set(['PETG'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('White PETG Print')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows widget when queue item has no filament overrides', async () => {
|
||||
// Default mockQueueItems have no filament_overrides
|
||||
render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilaments={new Set(['PLA:000000'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('First Print')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('matches any override when multiple overrides exist', async () => {
|
||||
const multiOverrideItem = [
|
||||
{
|
||||
id: 22,
|
||||
printer_id: null,
|
||||
archive_id: 22,
|
||||
position: 1,
|
||||
status: 'pending',
|
||||
archive_name: 'Multi Color Print',
|
||||
printer_name: null,
|
||||
print_time_seconds: 3600,
|
||||
scheduled_time: null,
|
||||
required_filament_types: ['PLA'],
|
||||
filament_overrides: [
|
||||
{ slot_id: 1, type: 'PLA', color: '#FF0000' },
|
||||
{ slot_id: 2, type: 'PLA', color: '#00FF00' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
server.use(
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json(multiOverrideItem))
|
||||
);
|
||||
|
||||
// Printer has green PLA but not red — should still match (at least one override)
|
||||
render(
|
||||
<PrinterQueueWidget
|
||||
printerId={1}
|
||||
printerState="FINISH"
|
||||
loadedFilamentTypes={new Set(['PLA'])}
|
||||
loadedFilaments={new Set(['PLA:00ff00'])}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Multi Color Print')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,9 +13,10 @@ interface PrinterQueueWidgetProps {
|
||||
printerState?: string | null;
|
||||
plateCleared?: boolean;
|
||||
loadedFilamentTypes?: Set<string>;
|
||||
loadedFilaments?: Set<string>; // "TYPE:rrggbb" pairs for filament override color matching
|
||||
}
|
||||
|
||||
export function PrinterQueueWidget({ printerId, printerModel, printerState, plateCleared, loadedFilamentTypes }: PrinterQueueWidgetProps) {
|
||||
export function PrinterQueueWidget({ printerId, printerModel, printerState, plateCleared, loadedFilamentTypes, loadedFilaments }: PrinterQueueWidgetProps) {
|
||||
const { t } = useTranslation();
|
||||
const queryClient = useQueryClient();
|
||||
const { showToast } = useToast();
|
||||
@@ -38,11 +39,25 @@ export function PrinterQueueWidget({ printerId, printerModel, printerState, plat
|
||||
},
|
||||
});
|
||||
|
||||
// Filter queue to items this printer can actually print (filament type check)
|
||||
// Filter queue to items this printer can actually print (filament type + color check)
|
||||
const compatibleQueue = queue?.filter(item => {
|
||||
if (!item.required_filament_types?.length) return true;
|
||||
if (!loadedFilamentTypes?.size) return true;
|
||||
return item.required_filament_types.every((t: string) => loadedFilamentTypes.has(t.toUpperCase()));
|
||||
// Type check: all required filament types must be loaded
|
||||
if (item.required_filament_types?.length && loadedFilamentTypes?.size) {
|
||||
if (!item.required_filament_types.every((t: string) => loadedFilamentTypes.has(t.toUpperCase()))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Color check: if filament overrides specify colors, at least one must match
|
||||
// Mirrors backend _count_override_color_matches() logic
|
||||
if (item.filament_overrides?.length && loadedFilaments?.size) {
|
||||
const hasColorMatch = item.filament_overrides.some(o => {
|
||||
const oType = (o.type || '').toUpperCase();
|
||||
const oColor = (o.color || '').replace('#', '').toLowerCase().slice(0, 6);
|
||||
return loadedFilaments.has(`${oType}:${oColor}`);
|
||||
});
|
||||
if (!hasColorMatch) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const nextItem = compatibleQueue?.[0];
|
||||
|
||||
@@ -1556,6 +1556,29 @@ function PrinterCard({
|
||||
return types;
|
||||
}, [status?.ams, status?.vt_tray]);
|
||||
|
||||
// Collect loaded filament type+color pairs for queue widget override matching
|
||||
// Format: "TYPE:rrggbb" (e.g., "PETG:ffffff") — mirrors backend _count_override_color_matches()
|
||||
const loadedFilaments = useMemo(() => {
|
||||
const filaments = new Set<string>();
|
||||
if (status?.ams) {
|
||||
for (const ams of status.ams) {
|
||||
for (const tray of ams.tray || []) {
|
||||
if (tray.tray_type && tray.tray_color) {
|
||||
const color = tray.tray_color.replace('#', '').toLowerCase().slice(0, 6);
|
||||
filaments.add(`${tray.tray_type.toUpperCase()}:${color}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const vt of status?.vt_tray ?? []) {
|
||||
if (vt.tray_type && vt.tray_color) {
|
||||
const color = vt.tray_color.replace('#', '').toLowerCase().slice(0, 6);
|
||||
filaments.add(`${vt.tray_type.toUpperCase()}:${color}`);
|
||||
}
|
||||
}
|
||||
return filaments;
|
||||
}, [status?.ams, status?.vt_tray]);
|
||||
|
||||
// Fetch cloud filament info for tooltips (name includes color, also has K value)
|
||||
const { data: filamentInfo } = useQuery({
|
||||
queryKey: ['filamentInfo', trayInfoIds],
|
||||
@@ -2489,7 +2512,7 @@ function PrinterCard({
|
||||
</div>
|
||||
|
||||
{/* Queue Widget - always visible when there are pending items */}
|
||||
<PrinterQueueWidget printerId={printer.id} printerModel={printer.model} printerState={status.state} plateCleared={status.plate_cleared} loadedFilamentTypes={loadedFilamentTypes} />
|
||||
<PrinterQueueWidget printerId={printer.id} printerModel={printer.model} printerState={status.state} plateCleared={status.plate_cleared} loadedFilamentTypes={loadedFilamentTypes} loadedFilaments={loadedFilaments} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user