From a98ec645e15351d10b08da651bd74234985c02c8 Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Sun, 28 Jun 2026 18:57:13 -0700 Subject: [PATCH 1/6] feat(AFC): Adding print dialog box for users that user AFC-Klipper-Add-On Signed-off-by: Jim Madill --- .../afc/dialogs/AfcPrintStartDialog.vue | 163 ++++++ .../afc/dialogs/AfcPrintStartDialogTool.vue | 471 ++++++++++++++++++ .../widgets/filesystem/FileSystem.vue | 27 +- .../widgets/status/PrinterStatusCard.vue | 30 +- src/locales/en.yaml | 12 + 5 files changed, 699 insertions(+), 4 deletions(-) create mode 100644 src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue create mode 100644 src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue new file mode 100644 index 0000000000..2b2fd036d7 --- /dev/null +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue new file mode 100644 index 0000000000..0b780c8164 --- /dev/null +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue @@ -0,0 +1,471 @@ + + + diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index 5731025460..e1def4d260 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -135,6 +135,8 @@ :root="currentRoot" @path-change="loadFiles" /> + + @@ -153,6 +155,8 @@ import FileEditorDialog from './FileEditorDialog.vue' import FileNameDialog from './FileNameDialog.vue' import FileSystemGoToFileDialog from './FileSystemGoToFileDialog.vue' import FilePreviewDialog from './FilePreviewDialog.vue' +import AfcPrintStartDialog from '@/components/widgets/afc/dialogs/AfcPrintStartDialog.vue' +import AfcMixin from '@/mixins/afc' import type { AppDataTableHeader, FileWithPath } from '@/types' import { getFilesFromDataTransfer, hasFilesInDataTransfer } from '@/util/file-system-entry' import { getFileDataTransferDataFromDataTransfer, hasFileDataTransferTypeInDataTransfer, setFileDataTransferDataInDataTransfer } from '@/util/file-data-transfer' @@ -175,10 +179,11 @@ import type { KlipperSaveAndRestartAction } from '@/store/config/types' FileEditorDialog, FileNameDialog, FileSystemGoToFileDialog, - FilePreviewDialog + FilePreviewDialog, + AfcPrintStartDialog } }) -export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesMixin) { +export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesMixin, AfcMixin) { // Can be a list of roots, or a single root. @Prop({ type: [String, Array], required: true }) readonly roots!: string | string[] @@ -929,6 +934,24 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM const filename = file.path ? `${file.path}/${file.filename}` : file.filename + // AFC check — show lane mapping dialog when AFC is installed. If metadata + // is not yet loaded we open the dialog anyway (it will fetch on open); + // if metadata is loaded we only show it for multi-tool prints. + if (this.afc != null) { + const hasMetadata = 'filament_weights' in file + const filamentWeights: number[] = hasMetadata ? (file.filament_weights ?? []) : [] + const usedTools = filamentWeights.filter(w => w > 0) + + if (!hasMetadata || usedTools.length > 0) { + this.$typedCommit('afc/setDialogState', { + show: true, + filename + }) + + return + } + } + if (this.$typedState.printer.printer.mmu?.enabled === true) { if ('referenced_tools' in file) { const mmuPrint = (file.referenced_tools?.length ?? 1) > 1 || this.$typedState.printer.printer.mmu?.gate !== -2 diff --git a/src/components/widgets/status/PrinterStatusCard.vue b/src/components/widgets/status/PrinterStatusCard.vue index d6d691ee03..c5cdcbe7fd 100644 --- a/src/components/widgets/status/PrinterStatusCard.vue +++ b/src/components/widgets/status/PrinterStatusCard.vue @@ -65,6 +65,8 @@ /> + + @@ -72,9 +74,11 @@ import { Component, Mixins, Watch } from 'vue-property-decorator' import { SocketActions } from '@/api/socketActions' import StateMixin from '@/mixins/state' +import AfcMixin from '@/mixins/afc' import StatusControls from './StatusControls.vue' import StatusTab from './StatusTab.vue' import ReprintTab from './ReprintTab.vue' +import AfcPrintStartDialog from '@/components/widgets/afc/dialogs/AfcPrintStartDialog.vue' import type { TimeEstimates } from '@/store/printer/types' import getFilePaths from '@/util/get-file-paths' @@ -82,10 +86,11 @@ import getFilePaths from '@/util/get-file-paths' components: { StatusControls, StatusTab, - ReprintTab + ReprintTab, + AfcPrintStartDialog } }) -export default class PrinterStatusCard extends Mixins(StateMixin) { +export default class PrinterStatusCard extends Mixins(StateMixin, AfcMixin) { tab = 0 // If the user has no history plugin, and there's no print running.. @@ -128,6 +133,27 @@ export default class PrinterStatusCard extends Mixins(StateMixin) { } handlePrint (filename: string) { + // AFC check — show lane mapping dialog when AFC is installed. If metadata + // is not yet loaded we open the dialog anyway (it will fetch on open); + // if metadata is loaded we only show it for multi-tool prints. + if (this.afc != null) { + const { rootPath, filename: filenameOnly } = getFilePaths(filename, 'gcodes') + const fileWithMeta = this.$typedGetters['files/getFile'](rootPath, filenameOnly) + + const hasMetadata = fileWithMeta != null && 'filament_weights' in fileWithMeta + const filamentWeights: number[] = hasMetadata ? (fileWithMeta.filament_weights ?? []) : [] + const usedTools = filamentWeights.filter((w: number) => w > 0) + + if (!hasMetadata || usedTools.length > 0) { + this.$typedCommit('afc/setDialogState', { + show: true, + filename + }) + + return + } + } + if (this.$typedState.printer.printer.mmu?.enabled === true) { const { rootPath, filename: filenameOnly } = getFilePaths(filename, 'gcodes') const fileWithMeta = this.$typedGetters['files/getFile'](rootPath, filenameOnly) diff --git a/src/locales/en.yaml b/src/locales/en.yaml index e0a5d8f989..7427c576db 100644 --- a/src/locales/en.yaml +++ b/src/locales/en.yaml @@ -39,6 +39,18 @@ app: PreExtruderSensor: "Pre-Extruder Sensor" PrepDetected: "Filament detected but not loaded" Printing: "Printing" + PrintStartDialog: + StartJob: "Start Job" + Title: "AFC Lane Assignment" + NoLane: "No Lane" + NoLaneMapped: "No lane mapped to {tool}" + FilamentTypeMismatch: "Filament material type mismatch: file requires {file}, lane has {lane}" + FilamentWeightNotEnough: "Not enough filament in lane {lane}: requires {required}, available {available}" + Question: "Do you want to start printing {filename}?" + QuestionWithSpool: "Do you want to start printing {filename} with the active spool?" + SpoolId: "ID #{id}" + SpoolWeightRemaining: "{remaining}g remaining ({used}g used)" + LaneWeightRemaining: "Weight remaining {weight}g" RammingSensor: "Ramming Sensor" Restoring: "Restoring" SetSpool: "Set Spool" From 4b5458336f382783539f8aee97e9af10f188b726 Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Fri, 3 Jul 2026 10:36:37 -0700 Subject: [PATCH 2/6] chore(AFC): Refactoring code that looks up spool data - Refactored code with the help from claude to clean up looking up spool data and added a common method into afc mixin to easily return all necessary information for lane to be used in the lane panel and AFC print dialog. Signed-off-by: Jim Madill --- .../widgets/afc/AfcCardUnitLaneBody.vue | 51 +++++-------- .../afc/dialogs/AfcPrintStartDialogTool.vue | 71 ++++++++----------- src/mixins/afc.ts | 58 +++++++++++++++ src/typings/klipper.d.ts | 16 +++++ 4 files changed, 122 insertions(+), 74 deletions(-) diff --git a/src/components/widgets/afc/AfcCardUnitLaneBody.vue b/src/components/widgets/afc/AfcCardUnitLaneBody.vue index 9ae9deb754..8b5ac7ded4 100644 --- a/src/components/widgets/afc/AfcCardUnitLaneBody.vue +++ b/src/components/widgets/afc/AfcCardUnitLaneBody.vue @@ -122,7 +122,7 @@ import { Component, Mixins, Prop, Watch } from 'vue-property-decorator' import StateMixin from '@/mixins/state' import AfcMixin from '@/mixins/afc' -import type { Spool, SpoolSelectionDialogState } from '@/store/spoolman/types' +import type { SpoolSelectionDialogState } from '@/store/spoolman/types' import AfcUnitLaneInfiniteDialog from '@/components/widgets/afc/dialogs/AfcUnitLaneInfiniteDialog.vue' import AfcUnitLaneFilamentDialog from '@/components/widgets/afc/dialogs/AfcUnitLaneFilamentDialog.vue' import AfcFilamentReel from './AfcFilamentReel.vue' @@ -151,73 +151,60 @@ export default class AfcCardUnitLaneBody extends Mixins(StateMixin, AfcMixin) { return this.lane?.runout_lane ?? 'NONE' } - get spoolId (): number | undefined { - return this.lane?.spool_id ?? undefined + get laneInfo (): Klipper.AfcSpoolLaneInfo { + return this.getAfcLaneInfo(this.name) } - get spool (): Spool | null { - if (!this.spoolId) return null + get spoolId (): number | undefined { + return this.laneInfo.spoolId + } - return this.$typedGetters['spoolman/getSpoolById'](this.spoolId) ?? null + get spool () { + return this.laneInfo.spool } get spoolColor (): string { - if ( - this.afc?.td1_present && - this.lane?.td1_color && - this.afcShowTd1Color - ) { - return `#${this.lane.td1_color}` - } - - return this.lane?.color || '#000000' + return this.laneInfo.color } get spoolRemainingWeight (): number | undefined { - return this.spool?.remaining_weight ?? this.lane?.weight + return this.laneInfo.remainingWeight } get spoolFullWeight (): number | undefined { - return this.spool?.initial_weight ?? this.lane?.initial_weight + return this.laneInfo.fullWeight } get spoolPercent (): number { - if (this.spoolRemainingWeight == null || this.spoolFullWeight == null) return 100 - if (this.spoolFullWeight === 0) return 100 - - return Math.round((this.spoolRemainingWeight / this.spoolFullWeight) * 100) + return this.laneInfo.spoolPercent } get spoolMaterial (): string { - return this.spool?.filament?.material ?? this.lane?.material ?? '' + return this.laneInfo.material } get spoolFilamentVendor (): string | undefined { - return this.spool?.filament?.vendor?.name + return this.laneInfo.filamentVendor } get spoolFilamentName (): string | undefined { - return this.spool?.filament?.name || - this.lane?.filament_name || - undefined + return this.laneInfo.filamentName } get spoolUrl (): string | undefined { - const base: string | undefined = this.$typedGetters['spoolman/getSpoolmanUrl'] - if (!base || !this.spoolId) return undefined - return `${base.replace(/\/$/, '')}/spool/show/${this.spoolId}` + return this.laneInfo.spoolUrl } get spoolExtruderTemp (): number | undefined { - return this.spool?.filament?.settings_extruder_temp + return this.laneInfo.extruderTemp } get spoolBedTemp (): number | undefined { - return this.spool?.filament?.settings_bed_temp + return this.laneInfo.bedTemp } get spoolUsedWeight (): number | undefined { - return this.spool?.used_weight + return this.laneInfo.usedWeight } get tdPresent (): boolean { diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue index 0b780c8164..c74b10688c 100644 --- a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue @@ -211,12 +211,8 @@ class="mr-2" style="min-width: 0; width: 16px; height: 16px; padding: 0;" /> - - {{ option.lane }} - - - {{ option.material }} - + {{ option.lane }} + {{ option.material }}
-
- {{ $t('app.afc.PrintStartDialog.SpoolId', { id: option.spoolId }) }} -
+
{{ $t('app.afc.PrintStartDialog.SpoolId', { id: option.spoolId }) }}
{{ option.vendorName }} — {{ option.filamentName }}
@@ -342,22 +336,26 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin }) } - get laneSpoolId (): number | undefined { + get laneInfo (): Klipper.AfcSpoolLaneInfo | undefined { if (!this.laneName) return undefined - return this.getAfcLaneObject(this.laneName)?.spool_id ?? undefined + + return this.getAfcLaneInfo(this.laneName) + } + + get laneSpoolId (): number | undefined { + return this.laneInfo?.spoolId } get laneSpool () { - if (!this.laneSpoolId) return null - return this.$typedGetters['spoolman/getSpoolById'](this.laneSpoolId) ?? null + return this.laneInfo?.spool ?? null } get laneSpoolVendorName (): string { - return this.laneSpool?.filament?.vendor?.name ?? this.$t('app.afc.Unknown') as string + return this.laneInfo?.filamentVendor ?? this.$t('app.afc.Unknown') as string } get laneSpoolFilamentName (): string { - return this.laneSpool?.filament?.name ?? this.$t('app.afc.Unknown') as string + return this.laneInfo?.filamentName ?? this.$t('app.afc.Unknown') as string } get hasLaneTooltipContent (): boolean { @@ -368,16 +366,13 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin } get laneFilament (): FilamentInfo | undefined { - if (!this.laneName) return undefined - - const lane = this.getAfcLaneObject(this.laneName) - const spool = this.laneSpool + if (!this.laneName || !this.laneInfo) return undefined return { - color: (spool?.filament?.color_hex ? `#${spool.filament.color_hex.replace(/^#/, '')}` : lane?.color) ?? '', - name: spool?.filament_name ?? '--', - material: spool?.filament?.material || lane?.material || '--', - weight: Number(spool?.remaining_weight ?? lane?.weight ?? 0) + color: this.laneInfo.color, + name: this.laneInfo.filamentName ?? '--', + material: this.laneInfo.material || '--', + weight: Number(this.laneInfo.remainingWeight ?? 0) } } @@ -431,30 +426,22 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin get laneOptions (): LaneOption[] { return this.afcLanes.map(lane => { - const laneObj = this.getAfcLaneObject(lane) - const spoolId = laneObj?.spool_id ?? undefined - const spool = spoolId ? (this.$typedGetters['spoolman/getSpoolById'](spoolId) ?? null) : null - - const color = spool?.filament?.color_hex - ? `#${spool.filament.color_hex.replace(/^#/, '')}` - : (laneObj?.color ?? undefined) - - const weight = Number(spool?.remaining_weight ?? laneObj?.weight ?? 0) - - const material = spool?.filament?.material || laneObj?.material || '--' - const hasContent = spool != null || material !== '--' || weight > 0 + const info = this.getAfcLaneInfo(lane) + const material = info.material || '--' + const weight = Number(info.remainingWeight ?? 0) + const hasContent = info.spool != null || material !== '--' || weight > 0 return { lane, - spoolId: spool?.id ?? null, - color, + spoolId: info.spoolId ?? null, + color: info.color, material, - vendorName: spool?.filament?.vendor?.name ?? this.$t('app.afc.Unknown') as string, - filamentName: spool?.filament?.name ?? this.$t('app.afc.Unknown') as string, - extruderTemp: spool?.filament?.settings_extruder_temp, - bedTemp: spool?.filament?.settings_bed_temp, + vendorName: info.filamentVendor ?? this.$t('app.afc.Unknown') as string, + filamentName: info.filamentName ?? this.$t('app.afc.Unknown') as string, + extruderTemp: info.extruderTemp, + bedTemp: info.bedTemp, weight, - usedWeight: Number(spool?.used_weight ?? 0), + usedWeight: Number(info.usedWeight ?? 0), hasContent } }) diff --git a/src/mixins/afc.ts b/src/mixins/afc.ts index 797bd7e1a2..87880c922b 100644 --- a/src/mixins/afc.ts +++ b/src/mixins/afc.ts @@ -1,5 +1,6 @@ import Vue from 'vue' import Component from 'vue-class-component' +import type { Spool } from '@/store/spoolman/types' @Component export default class AfcMixin extends Vue { @@ -137,4 +138,61 @@ export default class AfcMixin extends Vue { return printerSettings[`afc_hub ${hub.toLowerCase()}`] } + + // ─── Lane spool helpers ─────────────────────────────────────────────────── + // Single method returning all resolved lane + spool data so each component + // does not need to call individual accessors or implement its own look-ups. + + getAfcLaneInfo (lane: string): Klipper.AfcSpoolLaneInfo { + const laneObj = this.getAfcLaneObject(lane) + + const spoolId = laneObj?.spool_id ?? undefined + const spool: Spool | null = spoolId + ? (this.$typedGetters['spoolman/getSpoolById'](spoolId) ?? null) + : null + + // Color: td1_color (when enabled) → spoolman color_hex → lane color + let color: string + if (this.afc?.td1_present && laneObj?.td1_color && this.afcShowTd1Color) { + color = `#${laneObj.td1_color}` + } else if (spool?.filament?.color_hex) { + color = `#${spool.filament.color_hex.replace(/^#/, '')}` + } else { + color = laneObj?.color || '#000000' + } + + const material = spool?.filament?.material || laneObj?.material || '' + const remainingWeight = spool?.remaining_weight ?? laneObj?.weight + const fullWeight = spool?.initial_weight ?? laneObj?.initial_weight + + let spoolPercent = 100 + if (remainingWeight != null && fullWeight != null && fullWeight > 0) { + spoolPercent = Math.round((remainingWeight / fullWeight) * 100) + } + + const spoolmanBase: string | undefined = this.$typedGetters['spoolman/getSpoolmanUrl'] + const spoolUrl = spoolmanBase && spoolId + ? `${spoolmanBase.replace(/\/$/, '')}/spool/show/${spoolId}` + : undefined + const usedWeight = spool?.used_weight ?? + (fullWeight != null && remainingWeight != null + ? fullWeight - remainingWeight + : undefined) + + return { + spoolId, + spool, + color, + material, + filamentVendor: spool?.filament?.vendor?.name, + filamentName: spool?.filament?.name || laneObj?.filament_name || undefined, + remainingWeight, + fullWeight, + spoolPercent, + usedWeight, + extruderTemp: spool?.filament?.settings_extruder_temp, + bedTemp: spool?.filament?.settings_bed_temp, + spoolUrl + } + } } diff --git a/src/typings/klipper.d.ts b/src/typings/klipper.d.ts index 83c886c3ce..d601c1ae1c 100644 --- a/src/typings/klipper.d.ts +++ b/src/typings/klipper.d.ts @@ -1797,4 +1797,20 @@ declare namespace Klipper { pin: string; long_press_duration: number; } + + export interface AfcSpoolLaneInfo { + spoolId: number | undefined; + spool: import('@/store/spoolman/types').Spool | null; + color: string; + material: string; + filamentVendor: string | undefined; + filamentName: string | undefined; + remainingWeight: number | undefined; + fullWeight: number | undefined; + spoolPercent: number; + usedWeight: number | undefined; + extruderTemp: number | undefined; + bedTemp: number | undefined; + spoolUrl: string | undefined; + } } From f35d0abb111cce09430feb111983554ffc1e3ece Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Fri, 3 Jul 2026 16:12:47 -0700 Subject: [PATCH 3/6] chore(AFC) Fixing some logical issues - Reverted weight check - Added new warning for notifying user that filament is not loaded into lane. Signed-off-by: Jim Madill --- .../afc/dialogs/AfcPrintStartDialogTool.vue | 16 ++++++++--- .../widgets/filesystem/FileSystem.vue | 18 +++++-------- .../widgets/status/PrinterStatusCard.vue | 27 +++++++------------ src/locales/en.yaml | 1 + src/mixins/afc.ts | 23 +++++++++------- src/typings/klipper.d.ts | 1 + 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue index c74b10688c..461ebdb840 100644 --- a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue @@ -378,14 +378,15 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin get isFilamentTypeValid (): boolean { if (!this.laneFilament) return false - if (this.fileFilament.material === '--' || this.laneFilament.material === '--') return true + if (this.fileFilament.material === '--' || this.laneFilament.material === '--') return false - return this.fileFilament.material.toLowerCase() === this.laneFilament.material.toLowerCase() + const laneMaterialLower = this.laneFilament.material.toLowerCase() + + return laneMaterialLower.includes(this.fileFilament.material.toLowerCase()) } get isFilamentWeightValid (): boolean { if (!this.laneFilament) return false - if (this.fileFilament.weight === 0 || this.laneFilament.weight === 0) return true return this.fileFilament.weight <= this.laneFilament.weight } @@ -402,6 +403,15 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin return warnings } + if (!this.laneInfo?.filamentLoaded) { + warnings.push( + this.$t('app.afc.PrintStartDialog.NoFilamentLoaded', { + lane: this.laneName + }) as string + ) + return warnings + } + if (!this.isFilamentTypeValid) { warnings.push( this.$t('app.afc.PrintStartDialog.FilamentTypeMismatch', { diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index e1def4d260..0b2b41e14b 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -937,19 +937,13 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM // AFC check — show lane mapping dialog when AFC is installed. If metadata // is not yet loaded we open the dialog anyway (it will fetch on open); // if metadata is loaded we only show it for multi-tool prints. - if (this.afc != null) { - const hasMetadata = 'filament_weights' in file - const filamentWeights: number[] = hasMetadata ? (file.filament_weights ?? []) : [] - const usedTools = filamentWeights.filter(w => w > 0) - - if (!hasMetadata || usedTools.length > 0) { - this.$typedCommit('afc/setDialogState', { - show: true, - filename - }) + if (this.shouldShowAfcDialog(file)) { + this.$typedCommit('afc/setDialogState', { + show: true, + filename + }) - return - } + return } if (this.$typedState.printer.printer.mmu?.enabled === true) { diff --git a/src/components/widgets/status/PrinterStatusCard.vue b/src/components/widgets/status/PrinterStatusCard.vue index c5cdcbe7fd..5d5b85a3e5 100644 --- a/src/components/widgets/status/PrinterStatusCard.vue +++ b/src/components/widgets/status/PrinterStatusCard.vue @@ -133,31 +133,22 @@ export default class PrinterStatusCard extends Mixins(StateMixin, AfcMixin) { } handlePrint (filename: string) { + const { rootPath, filename: filenameOnly } = getFilePaths(filename, 'gcodes') + const fileWithMeta = this.$typedGetters['files/getFile'](rootPath, filenameOnly) + // AFC check — show lane mapping dialog when AFC is installed. If metadata // is not yet loaded we open the dialog anyway (it will fetch on open); // if metadata is loaded we only show it for multi-tool prints. - if (this.afc != null) { - const { rootPath, filename: filenameOnly } = getFilePaths(filename, 'gcodes') - const fileWithMeta = this.$typedGetters['files/getFile'](rootPath, filenameOnly) - - const hasMetadata = fileWithMeta != null && 'filament_weights' in fileWithMeta - const filamentWeights: number[] = hasMetadata ? (fileWithMeta.filament_weights ?? []) : [] - const usedTools = filamentWeights.filter((w: number) => w > 0) - - if (!hasMetadata || usedTools.length > 0) { - this.$typedCommit('afc/setDialogState', { - show: true, - filename - }) + if (this.shouldShowAfcDialog(fileWithMeta)) { + this.$typedCommit('afc/setDialogState', { + show: true, + filename + }) - return - } + return } if (this.$typedState.printer.printer.mmu?.enabled === true) { - const { rootPath, filename: filenameOnly } = getFilePaths(filename, 'gcodes') - const fileWithMeta = this.$typedGetters['files/getFile'](rootPath, filenameOnly) - if (fileWithMeta != null && 'referenced_tools' in fileWithMeta) { const mmuPrint = (fileWithMeta.referenced_tools?.length ?? 1) > 1 || this.$typedState.printer.printer.mmu.gate !== -2 diff --git a/src/locales/en.yaml b/src/locales/en.yaml index 7427c576db..2f95abbf19 100644 --- a/src/locales/en.yaml +++ b/src/locales/en.yaml @@ -44,6 +44,7 @@ app: Title: "AFC Lane Assignment" NoLane: "No Lane" NoLaneMapped: "No lane mapped to {tool}" + NoFilamentLoaded: "No filament loaded into {lane}" FilamentTypeMismatch: "Filament material type mismatch: file requires {file}, lane has {lane}" FilamentWeightNotEnough: "Not enough filament in lane {lane}: requires {required}, available {available}" Question: "Do you want to start printing {filename}?" diff --git a/src/mixins/afc.ts b/src/mixins/afc.ts index 87880c922b..1232f41204 100644 --- a/src/mixins/afc.ts +++ b/src/mixins/afc.ts @@ -1,6 +1,7 @@ import Vue from 'vue' import Component from 'vue-class-component' import type { Spool } from '@/store/spoolman/types' +import type { AppFile, AppFileWithMeta } from '@/store/files/types' @Component export default class AfcMixin extends Vue { @@ -174,25 +175,29 @@ export default class AfcMixin extends Vue { const spoolUrl = spoolmanBase && spoolId ? `${spoolmanBase.replace(/\/$/, '')}/spool/show/${spoolId}` : undefined - const usedWeight = spool?.used_weight ?? - (fullWeight != null && remainingWeight != null - ? fullWeight - remainingWeight - : undefined) return { spoolId, spool, color, material, - filamentVendor: spool?.filament?.vendor?.name, + filamentVendor: spool?.filament?.vendor?.name ?? undefined, filamentName: spool?.filament?.name || laneObj?.filament_name || undefined, remainingWeight, fullWeight, spoolPercent, - usedWeight, - extruderTemp: spool?.filament?.settings_extruder_temp, - bedTemp: spool?.filament?.settings_bed_temp, - spoolUrl + usedWeight: spool?.used_weight ?? undefined, + extruderTemp: spool?.filament?.settings_extruder_temp ?? laneObj?.extruder_temp ?? undefined, + bedTemp: spool?.filament?.settings_bed_temp ?? undefined, + spoolUrl, + filamentLoaded: laneObj ? (laneObj?.prep && laneObj?.load) : undefined } } + + shouldShowAfcDialog (fileWithMeta: AppFileWithMeta | AppFile | undefined): boolean { + if (this.afc == null) return false + const hasMetadata = fileWithMeta != null && 'filament_weights' in fileWithMeta + const usedTools = hasMetadata ? (fileWithMeta!.filament_weights ?? []).filter(w => w > 0) : [] + return !hasMetadata || usedTools.length > 0 + } } diff --git a/src/typings/klipper.d.ts b/src/typings/klipper.d.ts index d601c1ae1c..58b35dac14 100644 --- a/src/typings/klipper.d.ts +++ b/src/typings/klipper.d.ts @@ -1812,5 +1812,6 @@ declare namespace Klipper { extruderTemp: number | undefined; bedTemp: number | undefined; spoolUrl: string | undefined; + filamentLoaded: boolean | undefined; } } From 56de639cbbc7777a3a7fa7892222f5378b624f12 Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Sat, 4 Jul 2026 07:21:44 -0700 Subject: [PATCH 4/6] (chore:AFC): Updating comment to be more clear for when AfcPrintDialog is shown. Signed-off-by: Jim Madill --- src/components/widgets/filesystem/FileSystem.vue | 4 +++- src/components/widgets/status/PrinterStatusCard.vue | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index 0b2b41e14b..702c5e6228 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -936,7 +936,9 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM // AFC check — show lane mapping dialog when AFC is installed. If metadata // is not yet loaded we open the dialog anyway (it will fetch on open); - // if metadata is loaded we only show it for multi-tool prints. + // if metadata is loaded, this panel should show for both single-color + // and multi-color prints to allow the user to remap lanes(if needed) + // before starting their print. if (this.shouldShowAfcDialog(file)) { this.$typedCommit('afc/setDialogState', { show: true, diff --git a/src/components/widgets/status/PrinterStatusCard.vue b/src/components/widgets/status/PrinterStatusCard.vue index 5d5b85a3e5..9d741b61d8 100644 --- a/src/components/widgets/status/PrinterStatusCard.vue +++ b/src/components/widgets/status/PrinterStatusCard.vue @@ -138,7 +138,9 @@ export default class PrinterStatusCard extends Mixins(StateMixin, AfcMixin) { // AFC check — show lane mapping dialog when AFC is installed. If metadata // is not yet loaded we open the dialog anyway (it will fetch on open); - // if metadata is loaded we only show it for multi-tool prints. + // if metadata is loaded, this panel should show for both single-color + // and multi-color prints to allow the user to remap lanes(if needed) + // before starting their print. if (this.shouldShowAfcDialog(fileWithMeta)) { this.$typedCommit('afc/setDialogState', { show: true, From fe3b431a1e0738f48fcc840199e971469245dd46 Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Sat, 4 Jul 2026 13:51:23 -0700 Subject: [PATCH 5/6] (chore:AFC): Converting `as string` to `toString()` Signed-off-by: Jim Madill --- .../widgets/afc/dialogs/AfcPrintStartDialog.vue | 4 ++-- .../afc/dialogs/AfcPrintStartDialogTool.vue | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue index 2b2fd036d7..fffdf9164b 100644 --- a/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialog.vue @@ -93,12 +93,12 @@ export default class AfcPrintStartDialog extends Mixins(StateMixin, AfcMixin, Fi if (this.activeSpool) { return this.$t('app.afc.PrintStartDialog.QuestionWithSpool', { filename: this.shortFilename - }) as string + }).toString() } return this.$t('app.afc.PrintStartDialog.Question', { filename: this.shortFilename - }) as string + }).toString() } get thumbnailUrl (): string { diff --git a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue index 461ebdb840..839af7465d 100644 --- a/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue +++ b/src/components/widgets/afc/dialogs/AfcPrintStartDialogTool.vue @@ -351,11 +351,11 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin } get laneSpoolVendorName (): string { - return this.laneInfo?.filamentVendor ?? this.$t('app.afc.Unknown') as string + return this.laneInfo?.filamentVendor ?? this.$t('app.afc.Unknown').toString() } get laneSpoolFilamentName (): string { - return this.laneInfo?.filamentName ?? this.$t('app.afc.Unknown') as string + return this.laneInfo?.filamentName ?? this.$t('app.afc.Unknown').toString() } get hasLaneTooltipContent (): boolean { @@ -398,7 +398,7 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin warnings.push( this.$t('app.afc.PrintStartDialog.NoLaneMapped', { tool: this.toolName - }) as string + }).toString() ) return warnings } @@ -407,7 +407,7 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin warnings.push( this.$t('app.afc.PrintStartDialog.NoFilamentLoaded', { lane: this.laneName - }) as string + }).toString() ) return warnings } @@ -417,7 +417,7 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin this.$t('app.afc.PrintStartDialog.FilamentTypeMismatch', { file: this.fileFilament.material, lane: this.laneFilament?.material ?? '--' - }) as string + }).toString() ) } @@ -427,7 +427,7 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin lane: this.laneName, required: this.$filters.getReadableWeightString(this.fileFilament.weight), available: this.$filters.getReadableWeightString(this.laneFilament?.weight ?? 0) - }) as string + }).toString() ) } @@ -446,8 +446,8 @@ export default class AfcPrintStartDialogTool extends Mixins(StateMixin, AfcMixin spoolId: info.spoolId ?? null, color: info.color, material, - vendorName: info.filamentVendor ?? this.$t('app.afc.Unknown') as string, - filamentName: info.filamentName ?? this.$t('app.afc.Unknown') as string, + vendorName: info.filamentVendor ?? this.$t('app.afc.Unknown').toString(), + filamentName: info.filamentName ?? this.$t('app.afc.Unknown').toString(), extruderTemp: info.extruderTemp, bedTemp: info.bedTemp, weight, From 6d73c3c00384994fa5a7ecd5edb75151e8fa0b2e Mon Sep 17 00:00:00 2001 From: jimmyjon711 Date: Sun, 5 Jul 2026 15:38:25 -0700 Subject: [PATCH 6/6] (chore:AFC): Moving AfcPrintStartDialog to App.vue Signed-off-by: Jim Madill --- src/App.vue | 5 ++++- src/components/widgets/filesystem/FileSystem.vue | 6 +----- src/components/widgets/status/PrinterStatusCard.vue | 6 +----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/App.vue b/src/App.vue index bcd115565a..de67b825c1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -75,6 +75,7 @@ + @@ -105,6 +106,7 @@ import ActionCommandPromptDialog from '@/components/common/ActionCommandPromptDi import KeyboardShortcutsDialog from '@/components/common/KeyboardShortcutsDialog.vue' import { eventTargetIsContentEditable, keyboardEventToKeyboardShortcut } from '@/util/event-helpers' import MmuEditTtgMapDialog from './components/widgets/mmu/MmuEditTtgMapDialog.vue' +import AfcPrintStartDialog from './components/widgets/afc/dialogs/AfcPrintStartDialog.vue' @Component({ metaInfo () { @@ -120,7 +122,8 @@ import MmuEditTtgMapDialog from './components/widgets/mmu/MmuEditTtgMapDialog.vu FileSystemUploadDialog, ActionCommandPromptDialog, KeyboardShortcutsDialog, - MmuEditTtgMapDialog + MmuEditTtgMapDialog, + AfcPrintStartDialog } }) export default class App extends Mixins(StateMixin, FilesMixin, BrowserMixin) { diff --git a/src/components/widgets/filesystem/FileSystem.vue b/src/components/widgets/filesystem/FileSystem.vue index 702c5e6228..609c93d4f0 100644 --- a/src/components/widgets/filesystem/FileSystem.vue +++ b/src/components/widgets/filesystem/FileSystem.vue @@ -135,8 +135,6 @@ :root="currentRoot" @path-change="loadFiles" /> - - @@ -155,7 +153,6 @@ import FileEditorDialog from './FileEditorDialog.vue' import FileNameDialog from './FileNameDialog.vue' import FileSystemGoToFileDialog from './FileSystemGoToFileDialog.vue' import FilePreviewDialog from './FilePreviewDialog.vue' -import AfcPrintStartDialog from '@/components/widgets/afc/dialogs/AfcPrintStartDialog.vue' import AfcMixin from '@/mixins/afc' import type { AppDataTableHeader, FileWithPath } from '@/types' import { getFilesFromDataTransfer, hasFilesInDataTransfer } from '@/util/file-system-entry' @@ -179,8 +176,7 @@ import type { KlipperSaveAndRestartAction } from '@/store/config/types' FileEditorDialog, FileNameDialog, FileSystemGoToFileDialog, - FilePreviewDialog, - AfcPrintStartDialog + FilePreviewDialog } }) export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesMixin, AfcMixin) { diff --git a/src/components/widgets/status/PrinterStatusCard.vue b/src/components/widgets/status/PrinterStatusCard.vue index 9d741b61d8..4f36019509 100644 --- a/src/components/widgets/status/PrinterStatusCard.vue +++ b/src/components/widgets/status/PrinterStatusCard.vue @@ -65,8 +65,6 @@ /> - - @@ -78,7 +76,6 @@ import AfcMixin from '@/mixins/afc' import StatusControls from './StatusControls.vue' import StatusTab from './StatusTab.vue' import ReprintTab from './ReprintTab.vue' -import AfcPrintStartDialog from '@/components/widgets/afc/dialogs/AfcPrintStartDialog.vue' import type { TimeEstimates } from '@/store/printer/types' import getFilePaths from '@/util/get-file-paths' @@ -86,8 +83,7 @@ import getFilePaths from '@/util/get-file-paths' components: { StatusControls, StatusTab, - ReprintTab, - AfcPrintStartDialog + ReprintTab } }) export default class PrinterStatusCard extends Mixins(StateMixin, AfcMixin) {