Browse Source

Show plate number on printer cards and actual printer in queue (#881)

  Printer cards and stream overlay now display the plate number when
  printing plate 2+ of a multi-plate 3MF (e.g. "MyModel — Plate 3").
  Queue items using model-based assignment ("Any P1S") now show the
  actual assigned printer name once the scheduler picks a printer,
  instead of continuing to show the generic target.
maziggy 1 month ago
parent
commit
6d40ee561c

+ 2 - 0
CHANGELOG.md

@@ -9,6 +9,8 @@ All notable changes to Bambuddy will be documented in this file.
 
 ### Improved
 - **Database Engine Info on System Page** — The System Information page now shows the active database engine (SQLite or PostgreSQL) and its version in the Database section, making it easy to verify which backend is in use.
+- **Plate Number in Printer View** ([#881](https://github.com/maziggy/bambuddy/issues/881)) — Printer cards and the stream overlay now show the plate number alongside the filename when printing plate 2+ of a multi-plate 3MF file (e.g. "MyModel — Plate 3"). Single-plate prints are unchanged.
+- **Printer Name in Queue for Model-Based Jobs** ([#881](https://github.com/maziggy/bambuddy/issues/881)) — Queue items assigned to a printer type ("Any P1S") now show the actual printer name once the scheduler assigns a specific printer, instead of continuing to display the generic model target while printing or in history.
 - **REST Smart Plug: Separate Power/Energy URLs and Unit Multipliers** ([#472](https://github.com/maziggy/bambuddy/issues/472)) — REST/Webhook smart plugs can now use individual URLs for power and energy data instead of requiring all values in a single status response. Each value falls back to the shared Status URL when no separate URL is configured, so existing setups work without changes. Added power and energy multipliers for unit conversion (e.g., set energy multiplier to `0.001` to convert Wh to kWh). Useful for platforms like ioBroker that expose each data point as a separate API endpoint.
 
 ### Fixed

+ 1 - 0
frontend/src/i18n/locales/de.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'Klicken, um HMS-Fehler anzuzeigen',
     estimatedCompletion: 'Geschätzte Fertigstellungszeit',
+    plateNumber: 'Platte {{number}}',
     slotOptions: 'Slot-Optionen',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/en.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'Click to view HMS errors',
     estimatedCompletion: 'Estimated completion time',
+    plateNumber: 'Plate {{number}}',
     slotOptions: 'Slot options',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/fr.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'Cliquez pour voir les erreurs HMS',
     estimatedCompletion: 'Fin estimée',
+    plateNumber: 'Plaque {{number}}',
     slotOptions: 'Options du slot',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/it.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'Clicca per vedere errori HMS',
     estimatedCompletion: 'Tempo completamento stimato',
+    plateNumber: 'Piastra {{number}}',
     slotOptions: 'Opzioni slot',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/ja.ts

@@ -499,6 +499,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'クリックしてHMSエラーを表示',
     estimatedCompletion: '完了予定時刻',
+    plateNumber: 'プレート {{number}}',
     slotOptions: 'スロットオプション',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/pt-BR.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: 'Clique para ver erros do HMS',
     estimatedCompletion: 'Tempo estimado de conclusão',
+    plateNumber: 'Placa {{number}}',
     slotOptions: 'Opções de slot',
     // AMS hover popup
     amsPopup: {

+ 1 - 0
frontend/src/i18n/locales/zh-CN.ts

@@ -500,6 +500,7 @@ export default {
     // HMS errors
     clickToViewHmsErrors: '点击查看 HMS 错误',
     estimatedCompletion: '预计完成时间',
+    plateNumber: '板 {{number}}',
     slotOptions: '槽位选项',
     // AMS hover popup
     amsPopup: {

+ 13 - 2
frontend/src/pages/PrintersPage.tsx

@@ -325,6 +325,17 @@ const BAMBU_COLOR_CODE_FALLBACK: Record<string, string> = {
   'T1': 'Gilded Rose', 'T2': 'Midnight Blaze', 'T3': 'Neon City', 'T4': 'Blue Hawaii', 'T5': 'Velvet Eclipse',
 };
 
+// Extract plate number from gcode_file path and append to print name
+function formatPrintName(name: string | null, gcodeFile: string | null | undefined, t: (key: string, fallback: string, opts?: Record<string, unknown>) => string): string {
+  if (!name) return '';
+  if (!gcodeFile) return name;
+  const match = gcodeFile.match(/plate_(\d+)\.gcode/);
+  if (match && parseInt(match[1], 10) > 1) {
+    return `${name} — ${t('printers.plateNumber', 'Plate {{number}}', { number: match[1] })}`;
+  }
+  return name;
+}
+
 // Get color name from Bambu Lab tray_id_name (e.g., "A00-Y2" -> "Sunflower Yellow")
 function getBambuColorName(trayIdName: string | null | undefined): string | null {
   if (!trayIdName) return null;
@@ -2783,7 +2794,7 @@ function PrinterCard({
                     {/* Cover Image */}
                     <CoverImage
                       url={(status.state === 'RUNNING' || status.state === 'PAUSE') ? status.cover_url : null}
-                      printName={(status.state === 'RUNNING' || status.state === 'PAUSE') ? (status.subtask_name || status.current_print || undefined) : undefined}
+                      printName={(status.state === 'RUNNING' || status.state === 'PAUSE') ? (formatPrintName(status.subtask_name || status.current_print || null, status.gcode_file, t) || undefined) : undefined}
                     />
                     {/* Print Info */}
                     <div className="flex-1 min-w-0">
@@ -2791,7 +2802,7 @@ function PrinterCard({
                         <>
                           <p className="text-sm text-bambu-gray mb-1">{getStatusDisplay(status.state, status.stg_cur_name)}</p>
                           <p className="text-white text-sm mb-2 truncate">
-                            {status.subtask_name || status.current_print}
+                            {formatPrintName(status.subtask_name || status.current_print || null, status.gcode_file, t)}
                           </p>
                           <div className="flex items-center justify-between text-sm">
                             <div className="flex-1 bg-bambu-dark-tertiary rounded-full h-2 mr-3">

+ 2 - 2
frontend/src/pages/QueuePage.tsx

@@ -488,10 +488,10 @@ function SortableQueueItem({
           </div>
 
           <div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:text-sm text-bambu-gray">
-            <span className={`flex items-center gap-1 sm:gap-1.5 ${item.printer_id === null && !item.target_model ? 'text-orange-400' : ''} ${item.target_model ? 'text-blue-400' : ''}`}>
+            <span className={`flex items-center gap-1 sm:gap-1.5 ${item.printer_id === null && !item.target_model ? 'text-orange-400' : ''} ${item.target_model && !item.printer_id ? 'text-blue-400' : ''}`}>
               <Printer className="w-3 h-3 sm:w-3.5 sm:h-3.5" />
               <span className="truncate max-w-[120px] sm:max-w-none">
-              {item.target_model
+              {item.target_model && !item.printer_id
                 ? `${t('queue.filter.any')} ${item.target_model}${item.target_location ? ` @ ${item.target_location}` : ''}${item.required_filament_types?.length ? ` (${item.required_filament_types.join(', ')})` : ''}`
                 : item.printer_id === null
                   ? t('queue.filter.unassigned')

+ 11 - 1
frontend/src/pages/StreamOverlayPage.tsx

@@ -23,6 +23,16 @@ interface OverlayConfig {
   showPrinter: boolean;
 }
 
+function formatPrintName(name: string | null, gcodeFile: string | null | undefined, t: (key: string, fallback: string, opts?: Record<string, unknown>) => string): string {
+  if (!name) return '';
+  if (!gcodeFile) return name;
+  const match = gcodeFile.match(/plate_(\d+)\.gcode/);
+  if (match && parseInt(match[1], 10) > 1) {
+    return `${name} — ${t('printers.plateNumber', 'Plate {{number}}', { number: match[1] })}`;
+  }
+  return name;
+}
+
 function parseConfig(params: URLSearchParams): OverlayConfig {
   const show = params.get('show')?.split(',') || ['progress', 'layers', 'eta', 'filename', 'status'];
 
@@ -235,7 +245,7 @@ export function StreamOverlayPage() {
           {/* Filename */}
           {config.showFilename && status.current_print && (
             <div className={`${sizes.textLarge} text-white font-semibold mb-2 truncate drop-shadow-md`}>
-              {status.current_print.replace(/\.gcode\.3mf$|\.3mf$|\.gcode$/i, '')}
+              {formatPrintName(status.current_print.replace(/\.gcode\.3mf$|\.3mf$|\.gcode$/i, ''), status.gcode_file, t)}
             </div>
           )}
 

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-k-ZBfy94.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Bexverg5.js"></script>
+    <script type="module" crossorigin src="/assets/index-k-ZBfy94.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BGA3I7Jb.css">
   </head>
   <body>

Some files were not shown because too many files changed in this diff