diff --git a/backend/app/api/routes/settings.py b/backend/app/api/routes/settings.py index a3846eb06..9f4efb49d 100644 --- a/backend/app/api/routes/settings.py +++ b/backend/app/api/routes/settings.py @@ -87,6 +87,7 @@ async def get_settings( "spoolman_enabled", "spoolman_disable_weight_sync", "spoolman_report_partial_usage", + "disable_filament_warnings", "check_updates", "check_printer_firmware", "virtual_printer_enabled", @@ -237,6 +238,7 @@ async def get_spoolman_settings( spoolman_sync_mode = await get_setting(db, "spoolman_sync_mode") or "auto" spoolman_disable_weight_sync = await get_setting(db, "spoolman_disable_weight_sync") or "false" spoolman_report_partial_usage = await get_setting(db, "spoolman_report_partial_usage") or "true" + disable_filament_warnings = await get_setting(db, "disable_filament_warnings") or "false" return { "spoolman_enabled": spoolman_enabled, @@ -244,6 +246,7 @@ async def get_spoolman_settings( "spoolman_sync_mode": spoolman_sync_mode, "spoolman_disable_weight_sync": spoolman_disable_weight_sync, "spoolman_report_partial_usage": spoolman_report_partial_usage, + "disable_filament_warnings": disable_filament_warnings, } @@ -273,6 +276,8 @@ async def update_spoolman_settings( await set_setting(db, "spoolman_disable_weight_sync", settings["spoolman_disable_weight_sync"]) if "spoolman_report_partial_usage" in settings: await set_setting(db, "spoolman_report_partial_usage", settings["spoolman_report_partial_usage"]) + if "disable_filament_warnings" in settings: + await set_setting(db, "disable_filament_warnings", settings["disable_filament_warnings"]) await db.commit() db.expire_all() diff --git a/backend/app/schemas/settings.py b/backend/app/schemas/settings.py index 3c4df280a..9e83682c5 100644 --- a/backend/app/schemas/settings.py +++ b/backend/app/schemas/settings.py @@ -31,6 +31,10 @@ class AppSettings(BaseModel): default=True, description="Report Partial Usage for Failed Prints. When a print fails or is cancelled, report the estimated filament used up to that point based on layer progress.", ) + disable_filament_warnings: bool = Field( + default=False, + description="Disable insufficient filament warnings when printing or queueing prints", + ) # Updates check_updates: bool = Field(default=True, description="Automatically check for updates on startup") @@ -158,6 +162,7 @@ class AppSettingsUpdate(BaseModel): spoolman_sync_mode: str | None = None spoolman_disable_weight_sync: bool | None = None spoolman_report_partial_usage: bool | None = None + disable_filament_warnings: bool | None = None check_updates: bool | None = None check_printer_firmware: bool | None = None notification_language: str | None = None diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 6be62e627..bd45f779c 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -748,6 +748,8 @@ export interface AppSettings { // Date/time format settings date_format: 'system' | 'us' | 'eu' | 'iso'; time_format: 'system' | '12h' | '24h'; + // Filament tracking + disable_filament_warnings: boolean; // Disable insufficient filament warnings when printing/queueing // Default printer default_printer_id: number | null; // Dark mode theme settings @@ -3362,9 +3364,9 @@ export const api = { body: JSON.stringify({ tray_uuid: trayUuid }), }), getSpoolmanSettings: () => - request<{ spoolman_enabled: string; spoolman_url: string; spoolman_sync_mode: string; spoolman_disable_weight_sync: string; spoolman_report_partial_usage: string; }>('/settings/spoolman'), - updateSpoolmanSettings: (data: { spoolman_enabled?: string; spoolman_url?: string; spoolman_sync_mode?: string; spoolman_disable_weight_sync?: string; spoolman_report_partial_usage?: string; }) => - request<{ spoolman_enabled: string; spoolman_url: string; spoolman_sync_mode: string; spoolman_disable_weight_sync: string; spoolman_report_partial_usage: string; }>('/settings/spoolman', { + request<{ spoolman_enabled: string; spoolman_url: string; spoolman_sync_mode: string; spoolman_disable_weight_sync: string; spoolman_report_partial_usage: string; disable_filament_warnings: string; }>('/settings/spoolman'), + updateSpoolmanSettings: (data: { spoolman_enabled?: string; spoolman_url?: string; spoolman_sync_mode?: string; spoolman_disable_weight_sync?: string; spoolman_report_partial_usage?: string; disable_filament_warnings?: string; }) => + request<{ spoolman_enabled: string; spoolman_url: string; spoolman_sync_mode: string; spoolman_disable_weight_sync: string; spoolman_report_partial_usage: string; disable_filament_warnings: string; }>('/settings/spoolman', { method: 'PUT', body: JSON.stringify(data), }), diff --git a/frontend/src/components/AssignSpoolModal.tsx b/frontend/src/components/AssignSpoolModal.tsx index 10011af81..5b08c1588 100644 --- a/frontend/src/components/AssignSpoolModal.tsx +++ b/frontend/src/components/AssignSpoolModal.tsx @@ -5,6 +5,7 @@ import { X, Loader2, Package, Check, Search } from 'lucide-react'; import { api } from '../api/client'; import type { InventorySpool, SpoolAssignment } from '../api/client'; import { Button } from './Button'; +import { ConfirmModal } from './ConfirmModal'; import { useToast } from '../contexts/ToastContext'; interface AssignSpoolModalProps { @@ -26,6 +27,8 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr const { showToast } = useToast(); const [selectedSpoolId, setSelectedSpoolId] = useState(null); const [searchFilter, setSearchFilter] = useState(''); + const [pendingAssignId, setPendingAssignId] = useState(null); + const [showMismatchConfirm, setShowMismatchConfirm] = useState(false); const { data: spools, isLoading } = useQuery({ queryKey: ['inventory-spools'], @@ -53,6 +56,8 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr }); queryClient.invalidateQueries({ queryKey: ['spool-assignments'] }); showToast(t('inventory.assignSuccess'), 'success'); + setShowMismatchConfirm(false); + setPendingAssignId(null); onClose(); }, onError: (error: Error) => { @@ -86,10 +91,35 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr ); }); + const normalizeMaterial = (value: string | undefined | null) => + (value ?? '').trim().toUpperCase(); + + const materialsMatch = (spoolMaterial: string, trayMaterial: string) => { + const normalizedSpool = normalizeMaterial(spoolMaterial); + const normalizedTray = normalizeMaterial(trayMaterial); + if (!normalizedSpool || !normalizedTray) return true; + if (normalizedTray.includes(normalizedSpool)) return true; + if (normalizedSpool.includes(normalizedTray)) return true; + return false; + }; + const handleAssign = () => { - if (selectedSpoolId) { - assignMutation.mutate(selectedSpoolId); + if (!selectedSpoolId) return; + const selectedSpool = spools?.find((spool: InventorySpool) => spool.id === selectedSpoolId); + if (selectedSpool && trayInfo?.type) { + const mismatch = !materialsMatch(selectedSpool.material, trayInfo.type); + if (mismatch) { + setPendingAssignId(selectedSpoolId); + setShowMismatchConfirm(true); + return; + } } + assignMutation.mutate(selectedSpoolId); + }; + + const handleConfirmMismatch = () => { + if (!pendingAssignId) return; + assignMutation.mutate(pendingAssignId); }; return ( @@ -228,6 +258,27 @@ export function AssignSpoolModal({ isOpen, onClose, printerId, amsId, trayId, tr )} + + {showMismatchConfirm && trayInfo && selectedSpoolId && ( + spool.id === selectedSpoolId)?.material ?? '', + trayMaterial: trayInfo.type, + location: trayInfo.location, + })} + confirmText={t('inventory.assignMismatchConfirm')} + variant="warning" + isLoading={assignMutation.isPending} + onConfirm={handleConfirmMismatch} + onCancel={() => { + if (!assignMutation.isPending) { + setShowMismatchConfirm(false); + setPendingAssignId(null); + } + }} + /> + )} ); } diff --git a/frontend/src/components/ConfirmModal.tsx b/frontend/src/components/ConfirmModal.tsx index 93da3b54e..a9f697f48 100644 --- a/frontend/src/components/ConfirmModal.tsx +++ b/frontend/src/components/ConfirmModal.tsx @@ -51,7 +51,7 @@ export function ConfirmModal({ }, warning: { icon: 'text-yellow-400', - button: 'bg-yellow-500 hover:bg-yellow-600', + button: 'bg-yellow-500 hover:bg-yellow-600 text-black', }, default: { icon: 'text-bambu-green', @@ -77,7 +77,7 @@ export function ConfirmModal({

{title}

-

{message}

+

{message}

diff --git a/frontend/src/components/PrintModal/index.tsx b/frontend/src/components/PrintModal/index.tsx index fc0698752..0c566c486 100644 --- a/frontend/src/components/PrintModal/index.tsx +++ b/frontend/src/components/PrintModal/index.tsx @@ -3,13 +3,14 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { X, Printer, Loader2, Calendar, Pencil, AlertCircle, AlertTriangle } from 'lucide-react'; import { api } from '../../api/client'; -import type { PrintQueueItemCreate, PrintQueueItemUpdate } from '../../api/client'; +import type { PrintQueueItemCreate, PrintQueueItemUpdate, SpoolAssignment } from '../../api/client'; import { Card, CardContent } from '../Card'; import { Button } from '../Button'; +import { ConfirmModal } from '../ConfirmModal'; import { useToast } from '../../contexts/ToastContext'; -import { useFilamentMapping } from '../../hooks/useFilamentMapping'; +import { buildLoadedFilaments, useFilamentMapping } from '../../hooks/useFilamentMapping'; import { useMultiPrinterFilamentMapping, type PerPrinterConfig } from '../../hooks/useMultiPrinterFilamentMapping'; -import { isPlaceholderDate } from '../../utils/amsHelpers'; +import { getGlobalTrayId, isPlaceholderDate } from '../../utils/amsHelpers'; import { toDateTimeLocalValue } from '../../utils/date'; import { PrinterSelector } from './PrinterSelector'; import { PlateSelector } from './PlateSelector'; @@ -50,6 +51,13 @@ export function PrintModal({ // Determine if we're printing a library file const isLibraryFile = !!libraryFileId && !archiveId; + type FilamentWarningItem = { + printerName: string; + slotLabel: string; + requiredGrams: number; + remainingGrams: number; + }; + // Multiple printer selection (used for all modes now) const [selectedPrinters, setSelectedPrinters] = useState(() => { // Initialize with the queue item's printer if editing @@ -155,6 +163,8 @@ export function PrintModal({ const [isSubmitting, setIsSubmitting] = useState(false); const [submitProgress, setSubmitProgress] = useState({ current: 0, total: 0 }); + const [filamentWarningItems, setFilamentWarningItems] = useState(null); + // Track which printers have had the "Expand custom mapping by default" setting applied // This ensures the setting only affects initial state, not preventing unchecking const [initialExpandApplied, setInitialExpandApplied] = useState>(new Set()); @@ -175,6 +185,13 @@ export function PrintModal({ queryFn: api.getPrinters, }); + const { data: spoolAssignments } = useQuery({ + queryKey: ['spool-assignments'], + queryFn: () => api.getAssignments(), + staleTime: 30 * 1000, + enabled: (mode === 'reprint' || mode === 'add-to-queue') && assignmentMode === 'printer', + }); + // Fetch archive details to get sliced_for_model const { data: archiveDetails } = useQuery({ queryKey: ['archive', archiveId], @@ -329,6 +346,35 @@ export function PrintModal({ const isMultiPlate = platesData?.is_multi_plate ?? false; const plates = platesData?.plates ?? []; + const spoolAssignmentsByPrinter = useMemo(() => { + const map = new Map>(); + if (!spoolAssignments) return map; + spoolAssignments.forEach((assignment) => { + const globalTrayId = getGlobalTrayId( + assignment.ams_id, + assignment.tray_id, + assignment.ams_id < 0 + ); + const printerMap = map.get(assignment.printer_id) ?? new Map(); + printerMap.set(globalTrayId, assignment); + map.set(assignment.printer_id, printerMap); + }); + return map; + }, [spoolAssignments]); + + const filamentWarningMessage = useMemo(() => { + if (!filamentWarningItems || filamentWarningItems.length === 0) return ''; + const lines = filamentWarningItems.map((item) => + t('printModal.insufficientFilamentLine', { + printer: item.printerName, + slot: item.slotLabel, + required: Math.round(item.requiredGrams), + remaining: Math.round(item.remainingGrams), + }) + ); + return [t('printModal.insufficientFilamentMessage'), ...lines].join('\n'); + }, [filamentWarningItems, t]); + // Add to queue mutation (single printer) const addToQueueMutation = useMutation({ mutationFn: (data: PrintQueueItemCreate) => api.addToQueue(data), @@ -348,9 +394,71 @@ export function PrintModal({ }, }); - const handleSubmit = async (e?: React.FormEvent) => { + const handleSubmit = async (e?: React.FormEvent, options?: { skipFilamentCheck?: boolean }) => { e?.preventDefault(); + if ( + !options?.skipFilamentCheck && + !settings?.disable_filament_warnings && + (mode === 'reprint' || mode === 'add-to-queue') && + assignmentMode === 'printer' + ) { + const warningItems: FilamentWarningItem[] = []; + const filamentReqs = effectiveFilamentReqs?.filaments ?? []; + + if (filamentReqs.length > 0 && spoolAssignmentsByPrinter.size > 0) { + const getRemainingWeight = (labelWeight: number, weightUsed: number) => { + if (!Number.isFinite(labelWeight) || labelWeight <= 0) return null; + if (!Number.isFinite(weightUsed) || weightUsed < 0) return null; + return Math.max(0, labelWeight - weightUsed); + }; + + for (const printerId of selectedPrinters) { + const printerMapping = selectedPrinters.length > 1 + ? multiPrinterMapping.getFinalMapping(printerId) + : amsMapping; + if (!printerMapping) continue; + + const printerStatusForWarning = selectedPrinters.length > 1 + ? multiPrinterMapping.printerResults.find((result) => result.printerId === printerId)?.status + : printerStatus; + + const loadedFilaments = buildLoadedFilaments(printerStatusForWarning); + const slotLabelByTray = new Map(loadedFilaments.map((f) => [f.globalTrayId, f.label])); + const assignments = spoolAssignmentsByPrinter.get(printerId); + const printerName = printers?.find((p) => p.id === printerId)?.name ?? `Printer ${printerId}`; + + if (!assignments) continue; + + filamentReqs.forEach((req) => { + if (!req.slot_id || req.slot_id <= 0) return; + const globalTrayId = printerMapping[req.slot_id - 1]; + if (!Number.isFinite(globalTrayId) || globalTrayId < 0) return; + + const assignment = assignments.get(globalTrayId); + const spool = assignment?.spool; + if (!spool) return; + + const remainingGrams = getRemainingWeight(spool.label_weight, spool.weight_used); + if (remainingGrams === null) return; + if (remainingGrams >= req.used_grams) return; + + warningItems.push({ + printerName, + slotLabel: slotLabelByTray.get(globalTrayId) ?? `Slot ${req.slot_id}`, + requiredGrams: req.used_grams, + remainingGrams, + }); + }); + } + } + + if (warningItems.length > 0) { + setFilamentWarningItems(warningItems); + return; + } + } + // Validate printer/model selection if (assignmentMode === 'printer' && selectedPrinters.length === 0) { showToast('Please select at least one printer', 'error'); @@ -738,6 +846,21 @@ export function PrintModal({ + + {filamentWarningItems && filamentWarningItems.length > 0 && ( + { + setFilamentWarningItems(null); + void handleSubmit(undefined, { skipFilamentCheck: true }); + }} + onCancel={() => setFilamentWarningItems(null)} + /> + )}
); } diff --git a/frontend/src/components/SpoolmanSettings.tsx b/frontend/src/components/SpoolmanSettings.tsx index bc2645f68..526c2f0d2 100644 --- a/frontend/src/components/SpoolmanSettings.tsx +++ b/frontend/src/components/SpoolmanSettings.tsx @@ -17,6 +17,7 @@ export function SpoolmanSettings() { const [localSyncMode, setLocalSyncMode] = useState('auto'); const [localDisableWeightSync, setLocalDisableWeightSync] = useState(false); const [localReportPartialUsage, setLocalReportPartialUsage] = useState(true); + const [localDisableFilamentWarnings, setLocalDisableFilamentWarnings] = useState(false); const [selectedPrinterId, setSelectedPrinterId] = useState('all'); const [isInitialized, setIsInitialized] = useState(false); const [showAllSkipped, setShowAllSkipped] = useState(false); @@ -48,6 +49,7 @@ export function SpoolmanSettings() { setLocalSyncMode(settings.spoolman_sync_mode || 'auto'); setLocalDisableWeightSync(settings.spoolman_disable_weight_sync === 'true'); setLocalReportPartialUsage(settings.spoolman_report_partial_usage !== 'false'); + setLocalDisableFilamentWarnings(settings.disable_filament_warnings === 'true'); setIsInitialized(true); } }, [settings]); @@ -62,7 +64,8 @@ export function SpoolmanSettings() { (settings.spoolman_url || '') !== localUrl || (settings.spoolman_sync_mode || 'auto') !== localSyncMode || (settings.spoolman_disable_weight_sync === 'true') !== localDisableWeightSync || - (settings.spoolman_report_partial_usage !== 'false') !== localReportPartialUsage; + (settings.spoolman_report_partial_usage !== 'false') !== localReportPartialUsage || + (settings.disable_filament_warnings === 'true') !== localDisableFilamentWarnings; if (hasChanges) { const timeoutId = setTimeout(() => { @@ -71,7 +74,7 @@ export function SpoolmanSettings() { return () => clearTimeout(timeoutId); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [localEnabled, localUrl, localSyncMode, localDisableWeightSync, localReportPartialUsage, isInitialized]); + }, [localEnabled, localUrl, localSyncMode, localDisableWeightSync, localReportPartialUsage, localDisableFilamentWarnings, isInitialized]); // Save mutation const saveMutation = useMutation({ @@ -82,10 +85,12 @@ export function SpoolmanSettings() { spoolman_sync_mode: localSyncMode, spoolman_disable_weight_sync: localDisableWeightSync ? 'true' : 'false', spoolman_report_partial_usage: localReportPartialUsage ? 'true' : 'false', + disable_filament_warnings: localDisableFilamentWarnings ? 'true' : 'false', }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['spoolman-settings'] }); queryClient.invalidateQueries({ queryKey: ['spoolman-status'] }); + queryClient.invalidateQueries({ queryKey: ['settings'] }); queryClient.invalidateQueries({ queryKey: ['spool-assignments'] }); showToast(t('settings.toast.settingsSaved')); }, @@ -234,6 +239,25 @@ export function SpoolmanSettings() { + {/* Disable Filament Warnings toggle - applies to both modes */} +
+
+

{t('settings.disableFilamentWarnings')}

+

+ {t('settings.disableFilamentWarningsDesc')} +

+
+ +
+ {/* Built-in Inventory details */} {!localEnabled && (
diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index a7bd69f44..5e5c3c7b2 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -1164,6 +1164,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Filament-Verfolgung', filamentTrackingDesc: 'Wählen Sie, wie Sie Ihre Filamentspulen verfolgen möchten. Sie können das integrierte Inventar oder einen externen Spoolman-Server verwenden.', + disableFilamentWarnings: 'Filament-Warnungen deaktivieren', + disableFilamentWarningsDesc: 'Keine Warnungen über unzureichendes Filament beim Drucken oder Einreihen anzeigen', trackingModeBuiltIn: 'Integriertes Inventar', trackingModeBuiltInDesc: 'RFID-Erkennung und Verbrauchserfassung inklusive', trackingModeSpoolmanDesc: 'Externer Filament-Management-Server', @@ -2486,6 +2488,9 @@ export default { unassignSpool: 'Zuweisung aufheben', assignSuccess: 'Spule zugewiesen und AMS-Slot konfiguriert', assignFailed: 'Spulenzuweisung fehlgeschlagen', + assignMismatchTitle: 'Material stimmt nicht überein', + assignMismatchMessage: 'Das Material der ausgewählten Spule ({{spoolMaterial}}) stimmt nicht mit dem AMS-Profil ({{trayMaterial}}) für {{location}} überein. Trotzdem zuweisen?', + assignMismatchConfirm: 'Trotzdem zuweisen', selectSpool: 'Wählen Sie eine Spule für diesen Slot', assigned: 'Zugewiesen', assigning: 'Wird zugewiesen...', @@ -2650,6 +2655,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Linke Düse', rightNozzleTooltip: 'Rechte Düse', + insufficientFilamentTitle: 'Nicht genug Filament', + insufficientFilamentMessage: 'Einige zugewiesene Spulen haben weniger Filament als dieser Druck benötigt:', + insufficientFilamentLine: '{{printer}} - {{slot}}: benötigt {{required}}g, verbleibend {{remaining}}g', + printAnyway: 'Trotzdem drucken', }, // Backup diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index d9db5d33d..1b4967689 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1164,6 +1164,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Filament Tracking', filamentTrackingDesc: 'Choose how to track your filament spools. You can use the built-in inventory or connect an external Spoolman server.', + disableFilamentWarnings: 'Disable filament warnings', + disableFilamentWarningsDesc: 'Don\'t show warnings about insufficient filament when printing or queueing', trackingModeBuiltIn: 'Built-in Inventory', trackingModeBuiltInDesc: 'RFID auto-matching and usage tracking included', trackingModeSpoolmanDesc: 'External filament management server', @@ -2486,6 +2488,9 @@ export default { unassignSpool: 'Unassign', assignSuccess: 'Spool assigned and AMS slot configured', assignFailed: 'Failed to assign spool', + assignMismatchTitle: 'Material mismatch', + assignMismatchMessage: 'The selected spool material ({{spoolMaterial}}) does not match the AMS profile ({{trayMaterial}}) for {{location}}. Assign anyway?', + assignMismatchConfirm: 'Assign Anyway', selectSpool: 'Select a spool to assign to this slot', assigned: 'Assigned', assigning: 'Assigning...', @@ -2654,6 +2659,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Left nozzle', rightNozzleTooltip: 'Right nozzle', + insufficientFilamentTitle: 'Not enough filament', + insufficientFilamentMessage: 'Some assigned spools have less filament remaining than this print needs:', + insufficientFilamentLine: '{{printer}} - {{slot}}: needs {{required}}g, remaining {{remaining}}g', + printAnyway: 'Print anyway', }, // Backup diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index e2af45b11..ec0b6553e 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -1160,6 +1160,8 @@ export default { // Filament Tracking Mode filamentTracking: 'Suivi de Filament', filamentTrackingDesc: 'Choisissez comment suivre vos bobines. Utilisez l\'inventaire intégré ou connectez un serveur Spoolman.', + disableFilamentWarnings: 'Désactiver les avertissements de filament', + disableFilamentWarningsDesc: 'Ne pas afficher les avertissements de filament insuffisant lors de l\'impression ou de la mise en file d\'attente', trackingModeBuiltIn: 'Inventaire Intégré', trackingModeBuiltInDesc: 'Correspondance RFID et suivi de consommation inclus', trackingModeSpoolmanDesc: 'Serveur de gestion externe', @@ -2482,6 +2484,9 @@ export default { unassignSpool: 'Désassigner', assignSuccess: 'Bobine assignée et slot AMS configuré', assignFailed: 'Échec assignation', + assignMismatchTitle: 'Matériau non correspondant', + assignMismatchMessage: 'Le matériau de la bobine sélectionnée ({{spoolMaterial}}) ne correspond pas au profil AMS ({{trayMaterial}}) pour {{location}}. Assigner quand même ?', + assignMismatchConfirm: 'Assigner quand même', selectSpool: 'Choisir une bobine pour ce slot', assigned: 'Assigné', assigning: 'Assignation...', @@ -2650,6 +2655,10 @@ export default { rightNozzle: 'D', leftNozzleTooltip: 'Buse gauche', rightNozzleTooltip: 'Buse droite', + insufficientFilamentTitle: 'Filament insuffisant', + insufficientFilamentMessage: 'Certaines bobines assignées ont moins de filament restant que nécessaire pour cette impression :', + insufficientFilamentLine: '{{printer}} - {{slot}} : nécessite {{required}}g, restant {{remaining}}g', + printAnyway: 'Imprimer quand même', }, // Backup diff --git a/frontend/src/i18n/locales/it.ts b/frontend/src/i18n/locales/it.ts index 4b3b5d16e..6eb750515 100644 --- a/frontend/src/i18n/locales/it.ts +++ b/frontend/src/i18n/locales/it.ts @@ -1094,6 +1094,17 @@ export default { turnOn: 'Accendi', turnOff: 'Spegni', }, + // Filament Tracking Mode + filamentTracking: 'Tracciamento filamento', + filamentTrackingDesc: 'Scegli come tracciare le tue bobine di filamento. Puoi utilizzare l\'inventario integrato o collegare un server Spoolman esterno.', + disableFilamentWarnings: 'Disabilita avvisi filamento', + disableFilamentWarningsDesc: 'Non mostrare avvisi per filamento insufficiente durante la stampa o l\'accodamento', + trackingModeBuiltIn: 'Inventario integrato', + trackingModeBuiltInDesc: 'Corrispondenza RFID automatica e tracciamento utilizzo inclusi', + trackingModeSpoolmanDesc: 'Server di gestione filamenti esterno', + builtInFeatureRfid: 'Rileva automaticamente le bobine RFID Bambu Lab nell\'AMS', + builtInFeatureUsage: 'Traccia il consumo di filamento per stampa', + builtInFeatureCatalog: 'Gestisci bobine, colori e profili K-factor', // Spoolman spoolmanEnabled: 'Abilita integrazione Spoolman', spoolmanUrl: 'URL Spoolman', @@ -2305,6 +2316,9 @@ export default { unassignSpool: 'Deassegna', assignSuccess: 'Bobina assegnata e slot AMS configurato', assignFailed: 'Assegnazione bobina fallita', + assignMismatchTitle: 'Materiale non corrispondente', + assignMismatchMessage: 'Il materiale della bobina selezionata ({{spoolMaterial}}) non corrisponde al profilo AMS ({{trayMaterial}}) per {{location}}. Assegnare comunque?', + assignMismatchConfirm: 'Assegna comunque', selectSpool: 'Seleziona una bobina da assegnare a questo slot', assigned: 'Assegnato', assigning: 'Assegnazione...', @@ -2378,6 +2392,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: 'Ugello sinistro', rightNozzleTooltip: 'Ugello destro', + insufficientFilamentTitle: 'Filamento insufficiente', + insufficientFilamentMessage: 'Alcune bobine assegnate hanno meno filamento rimanente di quanto necessario per questa stampa:', + insufficientFilamentLine: '{{printer}} - {{slot}}: necessita di {{required}}g, rimanenti {{remaining}}g', + printAnyway: 'Stampa comunque', }, // Backup diff --git a/frontend/src/i18n/locales/ja.ts b/frontend/src/i18n/locales/ja.ts index 4333be1c8..37d2dd27a 100644 --- a/frontend/src/i18n/locales/ja.ts +++ b/frontend/src/i18n/locales/ja.ts @@ -1404,6 +1404,8 @@ export default { // フィラメント追跡モード filamentTracking: 'フィラメント追跡', filamentTrackingDesc: 'フィラメントスプールの追跡方法を選択してください。内蔵インベントリまたは外部Spoolmanサーバーを使用できます。', + disableFilamentWarnings: 'フィラメント警告を無効化', + disableFilamentWarningsDesc: '印刷またはキュー追加時にフィラメント不足の警告を表示しない', trackingModeBuiltIn: '内蔵インベントリ', trackingModeBuiltInDesc: 'RFID自動検出と使用量追跡を含む', trackingModeSpoolmanDesc: '外部フィラメント管理サーバー', @@ -2417,6 +2419,9 @@ export default { unassignSpool: '割り当て解除', assignSuccess: 'スプールを割り当て、AMSスロットを設定しました', assignFailed: 'スプールの割り当てに失敗しました', + assignMismatchTitle: '素材が一致しません', + assignMismatchMessage: '選択したスプールの素材({{spoolMaterial}})が、{{location}} のAMSプロファイル({{trayMaterial}})と一致しません。割り当てを続行しますか?', + assignMismatchConfirm: '続行する', selectSpool: 'このスロットに割り当てるスプールを選択', assigned: '割り当て済み', assigning: '割り当て中...', @@ -2570,6 +2575,10 @@ export default { rightNozzle: 'R', leftNozzleTooltip: '左ノズル', rightNozzleTooltip: '右ノズル', + insufficientFilamentTitle: 'フィラメントが不足しています', + insufficientFilamentMessage: '割り当てられたスプールの一部は、この印刷に必要な量より残量が少ないです:', + insufficientFilamentLine: '{{printer}} - {{slot}}: 必要 {{required}}g、残り {{remaining}}g', + printAnyway: 'それでも印刷', }, backup: { restoreBackup: 'バックアップの復元',