feat(queue): show build plate type on queue items + print modal (#1281)

Reporter on a multi-printer farm with 40+-plate runs needed to walk to
  the printer with the right physical plate, but the queue and the
  scheduling modal didn't surface curr_bed_type the way the archive card
  already did.

  New utils/threemf_tools.extract_bed_type_from_3mf helper (per-plate) so
  both the queue API and the /plates endpoint can return per-plate values.
  PrintQueueItemResponse.bed_type populated from archive.bed_type /
  library_file.file_metadata['bed_type'] as the default, then overridden
  per-plate via the helper when item.plate_id is set. /archives/{id}/plates
  (and library equivalent) include bed_type in each plate object.

  Per-plate accuracy matters because archive.bed_type is captured at
  ingest as only the first plate's value (services/archive.py:235) -
  a 40-plate 3MF mixing PEI + Engineering returns PEI at the archive
  level for every plate. The helper re-reads the 3MF and returns the
  truth.

  Frontend: queue card meta row + PlateSelector per-plate row + PrintModal
  header all render bed icon + canonical label via the existing
  getBedTypeInfo() helper, same as the archive card.
This commit is contained in:
maziggy
2026-06-08 10:23:02 +02:00
parent 7b4c5b3c1f
commit 432080d500
14 changed files with 395 additions and 122 deletions
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import type { PlateSelectorProps } from './types';
import { formatDuration } from '../../utils/date';
import { withStreamToken } from '../../api/client';
import { getBedTypeInfo } from '../../utils/bedType';
/**
* Plate selection grid for multi-plate 3MF files.
@@ -96,6 +97,18 @@ export function PlateSelector({
: `${plate.filaments.length} filament${plate.filaments.length !== 1 ? 's' : ''}`}
{plate.print_time_seconds != null ? ` • ${formatDuration(plate.print_time_seconds)}` : ''}
</p>
{(() => {
// Per-plate bed-type badge so multi-plate prints make the
// required plate explicit at scheduling time (#1281).
const bed = getBedTypeInfo(plate.bed_type);
if (!bed) return null;
return (
<p className="text-xs text-bambu-gray flex items-center gap-1 mt-0.5 truncate" title={bed.label}>
<img src={bed.icon} alt="" className="w-3.5 h-3.5 object-contain flex-shrink-0" />
<span className="truncate">{bed.label}</span>
</p>
);
})()}
</div>
{!multiSelect && isSelected && (
<Check className="w-4 h-4 text-bambu-green flex-shrink-0" />
@@ -13,6 +13,7 @@ import { buildLoadedFilaments, useFilamentMapping } from '../../hooks/useFilamen
import { useMultiPrinterFilamentMapping, type PerPrinterConfig } from '../../hooks/useMultiPrinterFilamentMapping';
import { getColorName } from '../../utils/colors';
import { getCurrencySymbol } from '../../utils/currency';
import { getBedTypeInfo } from '../../utils/bedType';
import { toDateTimeLocalValue, parseUTCDate } from '../../utils/date';
import { getGlobalTrayId, isPlaceholderDate } from '../../utils/amsHelpers';
import { FilamentMapping } from './FilamentMapping';
@@ -979,6 +980,26 @@ export function PrintModal({
)}
</p>
{/* Build-plate badge for the selected (or sole) plate — surfaced
early so the user knows which plate to mount before scheduling
(#1281). PlateSelector renders its own per-plate badges for
multi-plate files; this badge covers the single-plate case and
the multi-plate case where exactly one plate is selected. */}
{(() => {
if (!plates.length) return null;
const target = selectedPlate != null
? plates.find(p => p.index === selectedPlate)
: plates[0];
const bed = getBedTypeInfo(target?.bed_type);
if (!bed) return null;
return (
<p className="flex items-center gap-1.5 text-xs text-bambu-gray -mt-2" title={bed.label}>
<img src={bed.icon} alt="" className="w-4 h-4 object-contain flex-shrink-0" />
<span className="truncate">{bed.label}</span>
</p>
);
})()}
{/* Plate selection - first so users know filament requirements before selecting printers */}
<PlateSelector
plates={plates}
@@ -111,6 +111,7 @@ export interface PlateInfo {
}>;
print_time_seconds: number | null;
filament_used_grams: number | null;
bed_type?: string | null; // Build plate type for this plate, e.g. "Textured PEI Plate" (#1281)
}
/**