printer.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. export function getPrinterImage(model: string | null | undefined): string {
  2. if (!model) return '/img/printers/default.png';
  3. const m = model.toLowerCase().replace(/\s+/g, '');
  4. if (m.includes('x1e')) return '/img/printers/x1e.png';
  5. if (m.includes('x1c') || m.includes('x1carbon')) return '/img/printers/x1c.png';
  6. if (m.includes('x1')) return '/img/printers/x1c.png';
  7. if (m.includes('x2d') || m === 'n6') return '/img/printers/x2d.png';
  8. if (m.includes('h2dpro') || m.includes('h2d-pro')) return '/img/printers/h2dpro.png';
  9. if (m.includes('h2d')) return '/img/printers/h2d.png';
  10. if (m.includes('h2c')) return '/img/printers/h2c.png';
  11. if (m.includes('h2s')) return '/img/printers/h2d.png';
  12. if (m.includes('p2s')) return '/img/printers/p1s.png';
  13. if (m.includes('p1s')) return '/img/printers/p1s.png';
  14. if (m.includes('p1p')) return '/img/printers/p1p.png';
  15. if (m.includes('a2l') || m === 'n9') return '/img/printers/a2l.png';
  16. if (m.includes('a1mini')) return '/img/printers/a1mini.png';
  17. if (m.includes('a1')) return '/img/printers/a1.png';
  18. return '/img/printers/default.png';
  19. }
  20. // G-code interchange families (#2578). Mirrors backend GCODE_COMPAT_FAMILIES
  21. // in backend/app/utils/printer_models.py — keep the two in sync. A sliced 3MF
  22. // may target a different model ONLY within its family; everything else is
  23. // exact-match only.
  24. const GCODE_COMPAT_FAMILIES: ReadonlyArray<ReadonlySet<string>> = [
  25. new Set(['X1', 'X1C', 'X1E', 'P1P', 'P1S']),
  26. ];
  27. /** True when G-code sliced for one model may be dispatched to the other.
  28. * Unknown/missing metadata on either side returns true (can't validate). */
  29. export function isGcodeCompatible(
  30. slicedForModel: string | null | undefined,
  31. targetModel: string | null | undefined,
  32. ): boolean {
  33. if (!slicedForModel || !targetModel) return true;
  34. const norm = (m: string) => m.trim().toUpperCase().replace(/[\s-]/g, '');
  35. const a = norm(slicedForModel);
  36. const b = norm(targetModel);
  37. if (a === b) return true;
  38. return GCODE_COMPAT_FAMILIES.some((family) => family.has(a) && family.has(b));
  39. }
  40. export function getWifiStrength(rssi: number): { labelKey: string; color: string; bars: number } {
  41. if (rssi >= -50) return { labelKey: 'printers.wifiSignal.excellent', color: 'text-bambu-green', bars: 4 };
  42. if (rssi >= -60) return { labelKey: 'printers.wifiSignal.good', color: 'text-bambu-green', bars: 3 };
  43. if (rssi >= -70) return { labelKey: 'printers.wifiSignal.fair', color: 'text-yellow-400', bars: 2 };
  44. if (rssi >= -80) return { labelKey: 'printers.wifiSignal.weak', color: 'text-orange-400', bars: 1 };
  45. return { labelKey: 'printers.wifiSignal.veryWeak', color: 'text-red-400', bars: 1 };
  46. }
  47. import type { PrintQueueItem } from '../api/client';
  48. /**
  49. * Filters queue items based on printer compatibility (filament types and colors).
  50. * Mirrors backend _find_idle_printer_for_model() logic.
  51. * @param items - Array of queue items to filter
  52. * @param loadedFilamentTypes - Set of loaded filament types (e.g., "PLA", "PETG")
  53. * @param loadedFilaments - Set of loaded filament type+color pairs (e.g., "PLA:ffffff", "PETG:ff0000")
  54. * @param loadedVariants - Set of loaded type+color+tray_info_idx triples
  55. * (e.g., "PLA:ffffff:GFA01"; the idx is "" for custom/third-party spools). Used to
  56. * distinguish Bambu PLA sub-variants (Basic GFA00 / Matte GFA01 / Silk GFA06) that
  57. * share a base type+colour, mirroring the backend _get_missing_force_color_slots (#2650).
  58. * When omitted, force matching falls back to type+colour so the hint is never stricter
  59. * than the data available.
  60. * @returns Array of compatible queue items
  61. */
  62. export function filterCompatibleQueueItems(
  63. items: PrintQueueItem[],
  64. loadedFilamentTypes?: Set<string>,
  65. loadedFilaments?: Set<string>,
  66. loadedVariants?: Set<string>
  67. ): PrintQueueItem[] {
  68. return items.filter(item => {
  69. // Type check: all required filament types must be loaded
  70. if (item.required_filament_types && item.required_filament_types.length > 0 && loadedFilamentTypes !== undefined) {
  71. if (!item.required_filament_types.every((t: string) => loadedFilamentTypes.has(t.toUpperCase()))) {
  72. return false;
  73. }
  74. }
  75. // Color check: evaluate force_color_match per slot
  76. // Only apply when loadedFilaments is provided (not undefined).
  77. // An empty Set means no filaments are loaded — force-matched slots cannot match.
  78. if (item.filament_overrides && item.filament_overrides.length > 0 && loadedFilaments !== undefined) {
  79. const forceOverrides = item.filament_overrides.filter(o => o.force_color_match === true);
  80. const prefOverrides = item.filament_overrides.filter(o => o.force_color_match !== true);
  81. // All force-matched slots must have an exact type+color match — and, when the
  82. // override carries a tray_info_idx, the same variant too (a loaded tray with a
  83. // blank idx still satisfies it, matching the backend's type+colour fallback).
  84. if (forceOverrides.length > 0) {
  85. const allForceMatch = forceOverrides.every(o => {
  86. const oType = (o.type || '').toUpperCase();
  87. const oColor = (o.color || '').replace('#', '').toLowerCase().slice(0, 6);
  88. const oIdx = o.tray_info_idx || '';
  89. // No variant on the override, or no variant data supplied → type+colour only.
  90. if (!oIdx || loadedVariants === undefined) {
  91. return loadedFilaments.has(`${oType}:${oColor}`);
  92. }
  93. // Variant-specific: same idx, or a same-colour tray that reports no idx.
  94. return loadedVariants.has(`${oType}:${oColor}:${oIdx}`) || loadedVariants.has(`${oType}:${oColor}:`);
  95. });
  96. if (!allForceMatch) return false;
  97. }
  98. // Preference-only overrides: at least one color must match (existing behaviour)
  99. if (prefOverrides.length > 0 && forceOverrides.length === 0) {
  100. const hasColorMatch = prefOverrides.some(o => {
  101. const oType = (o.type || '').toUpperCase();
  102. const oColor = (o.color || '').replace('#', '').toLowerCase().slice(0, 6);
  103. return loadedFilaments.has(`${oType}:${oColor}`);
  104. });
  105. if (!hasColorMatch) return false;
  106. }
  107. }
  108. return true;
  109. });
  110. }