mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 15:54:27 +02:00
● Add prefer lowest remaining filament in auto-matching (#805)
When multiple AMS spools match the same type/color criteria, an optional setting now prefers the spool with the lowest remaining filament. This helps consume partial spools before starting new ones. Sorting applies to all matching paths: queue scheduler, print modal, and multi-printer mapping. Unknown remain values (-1) sort to end.
This commit is contained in:
@@ -25,9 +25,12 @@ function makeFilament(overrides: Partial<LoadedFilament> & { globalTrayId: numbe
|
||||
colorName: 'White',
|
||||
amsId: 0,
|
||||
trayId: 0,
|
||||
isHt: false,
|
||||
isExternal: false,
|
||||
label: 'AMS1-T1',
|
||||
trayInfoIdx: '',
|
||||
extruderId: undefined,
|
||||
remain: -1,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -196,3 +199,51 @@ describe('autoMatchFilament', () => {
|
||||
expect(result!.globalTrayId).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// -- autoMatchFilament with preferLowest ------------------------------------
|
||||
|
||||
describe('autoMatchFilament preferLowest', () => {
|
||||
it('picks spool with lowest remain when enabled', () => {
|
||||
const filaments = [
|
||||
makeFilament({ globalTrayId: 0, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 80 }),
|
||||
makeFilament({ globalTrayId: 1, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 30 }),
|
||||
];
|
||||
const req = makeReq({ type: 'PLA', color: '#FF0000' });
|
||||
const result = autoMatchFilament(req, filaments, new Set(), true);
|
||||
expect(result).toBeDefined();
|
||||
expect(result!.globalTrayId).toBe(1); // 30% < 80%
|
||||
});
|
||||
|
||||
it('picks first spool when disabled (default behavior)', () => {
|
||||
const filaments = [
|
||||
makeFilament({ globalTrayId: 0, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 80 }),
|
||||
makeFilament({ globalTrayId: 1, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 30 }),
|
||||
];
|
||||
const req = makeReq({ type: 'PLA', color: '#FF0000' });
|
||||
const result = autoMatchFilament(req, filaments, new Set(), false);
|
||||
expect(result).toBeDefined();
|
||||
expect(result!.globalTrayId).toBe(0); // First match
|
||||
});
|
||||
|
||||
it('sorts unknown remain (-1) to end', () => {
|
||||
const filaments = [
|
||||
makeFilament({ globalTrayId: 0, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: -1 }),
|
||||
makeFilament({ globalTrayId: 1, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 50 }),
|
||||
];
|
||||
const req = makeReq({ type: 'PLA', color: '#FF0000' });
|
||||
const result = autoMatchFilament(req, filaments, new Set(), true);
|
||||
expect(result).toBeDefined();
|
||||
expect(result!.globalTrayId).toBe(1); // Known 50% over unknown
|
||||
});
|
||||
|
||||
it('still respects nozzle constraint with preferLowest', () => {
|
||||
const filaments = [
|
||||
makeFilament({ globalTrayId: 0, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 10, extruderId: 1 }),
|
||||
makeFilament({ globalTrayId: 1, type: 'PLA', color: '#FF0000', colorName: 'Red', remain: 80, extruderId: 0 }),
|
||||
];
|
||||
const req = makeReq({ type: 'PLA', color: '#FF0000', nozzle_id: 0 });
|
||||
const result = autoMatchFilament(req, filaments, new Set(), true);
|
||||
expect(result).toBeDefined();
|
||||
expect(result!.globalTrayId).toBe(1); // Only tray on correct nozzle
|
||||
});
|
||||
});
|
||||
|
||||
@@ -873,3 +873,59 @@ describe('X1C model tests (single nozzle, real data)', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeAmsMapping preferLowest', () => {
|
||||
it('picks spool with lowest remain when enabled', () => {
|
||||
const reqs = {
|
||||
filaments: [{ slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10 }],
|
||||
};
|
||||
const status = createPrinterStatus([
|
||||
{
|
||||
id: 0,
|
||||
tray: [
|
||||
{ id: 0, tray_type: 'PLA', tray_color: 'FF0000', remain: 80 },
|
||||
{ id: 1, tray_type: 'PLA', tray_color: 'FF0000', remain: 25 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = computeAmsMapping(reqs, status, true);
|
||||
expect(result).toEqual([1]); // Tray 1 has 25% remain
|
||||
});
|
||||
|
||||
it('picks first match when disabled', () => {
|
||||
const reqs = {
|
||||
filaments: [{ slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10 }],
|
||||
};
|
||||
const status = createPrinterStatus([
|
||||
{
|
||||
id: 0,
|
||||
tray: [
|
||||
{ id: 0, tray_type: 'PLA', tray_color: 'FF0000', remain: 80 },
|
||||
{ id: 1, tray_type: 'PLA', tray_color: 'FF0000', remain: 25 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = computeAmsMapping(reqs, status, false);
|
||||
expect(result).toEqual([0]); // First match (default)
|
||||
});
|
||||
|
||||
it('sorts unknown remain to end', () => {
|
||||
const reqs = {
|
||||
filaments: [{ slot_id: 1, type: 'PLA', color: '#FF0000', used_grams: 10 }],
|
||||
};
|
||||
const status = createPrinterStatus([
|
||||
{
|
||||
id: 0,
|
||||
tray: [
|
||||
{ id: 0, tray_type: 'PLA', tray_color: 'FF0000' }, // No remain (defaults to -1)
|
||||
{ id: 1, tray_type: 'PLA', tray_color: 'FF0000', remain: 60 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const result = computeAmsMapping(reqs, status, true);
|
||||
expect(result).toEqual([1]); // Known 60% over unknown
|
||||
});
|
||||
});
|
||||
|
||||
@@ -838,6 +838,7 @@ export interface AppSettings {
|
||||
time_format: 'system' | '12h' | '24h';
|
||||
// Filament tracking
|
||||
disable_filament_warnings: boolean; // Disable filament warnings (print insufficiency and assignment mismatch)
|
||||
prefer_lowest_filament: boolean; // When multiple spools match, prefer lowest remaining filament
|
||||
// Default printer
|
||||
default_printer_id: number | null;
|
||||
// Dark mode theme settings
|
||||
|
||||
@@ -107,7 +107,8 @@ function InlineMappingEditor({
|
||||
isManual = true;
|
||||
} else {
|
||||
const usedTrayIds = new Set<number>(Object.values(printerResult.config.manualMappings));
|
||||
loaded = autoMatchFilament(req, printerResult.loadedFilaments, usedTrayIds) as LoadedFilament | undefined;
|
||||
const cachedSettings = queryClient.getQueryData<{ prefer_lowest_filament?: boolean }>(['settings']);
|
||||
loaded = autoMatchFilament(req, printerResult.loadedFilaments, usedTrayIds, cachedSettings?.prefer_lowest_filament) as LoadedFilament | undefined;
|
||||
}
|
||||
|
||||
// Determine status
|
||||
|
||||
@@ -324,7 +324,7 @@ export function PrintModal({
|
||||
});
|
||||
|
||||
// Get AMS mapping from hook (only when single printer selected)
|
||||
const { amsMapping } = useFilamentMapping(effectiveFilamentReqs, printerStatus, manualMappings);
|
||||
const { amsMapping } = useFilamentMapping(effectiveFilamentReqs, printerStatus, manualMappings, settings?.prefer_lowest_filament);
|
||||
|
||||
// Multi-printer filament mapping (for per-printer configuration)
|
||||
const multiPrinterMapping = useMultiPrinterFilamentMapping(
|
||||
@@ -333,7 +333,8 @@ export function PrintModal({
|
||||
effectiveFilamentReqs,
|
||||
manualMappings,
|
||||
perPrinterConfigs,
|
||||
setPerPrinterConfigs
|
||||
setPerPrinterConfigs,
|
||||
settings?.prefer_lowest_filament,
|
||||
);
|
||||
|
||||
// Auto-select first plate when plates load (single or multi-plate)
|
||||
|
||||
@@ -37,6 +37,7 @@ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined):
|
||||
trayInfoIdx: tray.tray_info_idx || '',
|
||||
traySubBrands: tray.tray_sub_brands || '',
|
||||
extruderId: amsExtruderMap?.[String(amsUnit.id)],
|
||||
remain: tray.remain ?? -1,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -61,6 +62,7 @@ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined):
|
||||
trayInfoIdx: extTray.tray_info_idx || '',
|
||||
traySubBrands: extTray.tray_sub_brands || '',
|
||||
extruderId: hasDualNozzle ? (255 - trayId) : undefined,
|
||||
remain: extTray.remain ?? -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -86,7 +88,8 @@ export function buildLoadedFilaments(printerStatus: PrinterStatus | undefined):
|
||||
*/
|
||||
export function computeAmsMapping(
|
||||
filamentReqs: { filaments: FilamentRequirement[] } | undefined,
|
||||
printerStatus: PrinterStatus | undefined
|
||||
printerStatus: PrinterStatus | undefined,
|
||||
preferLowest?: boolean,
|
||||
): number[] | undefined {
|
||||
if (!filamentReqs?.filaments || filamentReqs.filaments.length === 0) return undefined;
|
||||
|
||||
@@ -109,6 +112,15 @@ export function computeAmsMapping(
|
||||
available = available.filter((f) => f.extruderId === req.nozzle_id);
|
||||
}
|
||||
|
||||
// Sort by remaining filament (ascending) so .find() picks the lowest-remain spool first
|
||||
if (preferLowest) {
|
||||
available = [...available].sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
|
||||
let idxMatch: LoadedFilament | undefined;
|
||||
let exactMatch: LoadedFilament | undefined;
|
||||
let similarMatch: LoadedFilament | undefined;
|
||||
@@ -122,6 +134,13 @@ export function computeAmsMapping(
|
||||
idxMatch = idxMatches[0];
|
||||
} else if (idxMatches.length > 1) {
|
||||
// Multiple trays with same tray_info_idx - use color matching among them
|
||||
if (preferLowest) {
|
||||
idxMatches.sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
exactMatch = idxMatches.find(
|
||||
(f) =>
|
||||
f.type?.toUpperCase() === req.type?.toUpperCase() &&
|
||||
@@ -212,6 +231,8 @@ export interface LoadedFilament {
|
||||
traySubBrands?: string;
|
||||
/** Extruder ID for dual-nozzle printers (0=right, 1=left) */
|
||||
extruderId?: number;
|
||||
/** Remaining filament percentage (0-100), -1 = unknown */
|
||||
remain: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +306,8 @@ export function useLoadedFilaments(
|
||||
export function useFilamentMapping(
|
||||
filamentReqs: FilamentRequirementsResponse | undefined,
|
||||
printerStatus: PrinterStatus | undefined,
|
||||
manualMappings: Record<number, number>
|
||||
manualMappings: Record<number, number>,
|
||||
preferLowest?: boolean,
|
||||
): UseFilamentMappingResult {
|
||||
const loadedFilaments = useLoadedFilaments(printerStatus);
|
||||
|
||||
@@ -345,6 +367,15 @@ export function useFilamentMapping(
|
||||
available = available.filter((f) => f.extruderId === req.nozzle_id);
|
||||
}
|
||||
|
||||
// Sort by remaining filament (ascending) so .find() picks the lowest-remain spool first
|
||||
if (preferLowest) {
|
||||
available = [...available].sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
|
||||
let idxMatch: LoadedFilament | undefined;
|
||||
let exactMatch: LoadedFilament | undefined;
|
||||
let similarMatch: LoadedFilament | undefined;
|
||||
@@ -358,6 +389,13 @@ export function useFilamentMapping(
|
||||
idxMatch = idxMatches[0];
|
||||
} else if (idxMatches.length > 1) {
|
||||
// Multiple trays with same tray_info_idx - use color matching among them
|
||||
if (preferLowest) {
|
||||
idxMatches.sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
exactMatch = idxMatches.find(
|
||||
(f) =>
|
||||
f.type?.toUpperCase() === req.type?.toUpperCase() &&
|
||||
@@ -431,7 +469,7 @@ export function useFilamentMapping(
|
||||
isManual: false,
|
||||
};
|
||||
});
|
||||
}, [filamentReqs, loadedFilaments, manualMappings]);
|
||||
}, [filamentReqs, loadedFilaments, manualMappings, preferLowest]);
|
||||
|
||||
// Build AMS mapping from matched filaments
|
||||
// Format: array matching 3MF filament slot structure
|
||||
|
||||
@@ -88,7 +88,8 @@ export interface UseMultiPrinterFilamentMappingResult {
|
||||
function computeMatchDetails(
|
||||
filamentReqs: FilamentRequirement[] | undefined,
|
||||
loadedFilaments: LoadedFilament[],
|
||||
manualMappings: Record<number, number>
|
||||
manualMappings: Record<number, number>,
|
||||
preferLowest?: boolean,
|
||||
): { exactMatches: number; typeOnlyMatches: number; missingTypes: number; totalSlots: number; status: PrinterMatchStatus } {
|
||||
if (!filamentReqs || filamentReqs.length === 0) {
|
||||
return { exactMatches: 0, typeOnlyMatches: 0, missingTypes: 0, totalSlots: 0, status: 'full' };
|
||||
@@ -133,6 +134,14 @@ function computeMatchDetails(
|
||||
}
|
||||
}
|
||||
|
||||
if (preferLowest) {
|
||||
candidates = [...candidates].sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
|
||||
const exactMatch = candidates.find(
|
||||
(f) =>
|
||||
f.type?.toUpperCase() === req.type?.toUpperCase() &&
|
||||
@@ -183,7 +192,8 @@ function computeMatchDetails(
|
||||
function computeMappingWithOverrides(
|
||||
filamentReqs: { filaments: FilamentRequirement[] } | undefined,
|
||||
printerStatus: PrinterStatus | undefined,
|
||||
manualMappings: Record<number, number>
|
||||
manualMappings: Record<number, number>,
|
||||
preferLowest?: boolean,
|
||||
): number[] | undefined {
|
||||
if (!filamentReqs?.filaments || filamentReqs.filaments.length === 0) return undefined;
|
||||
|
||||
@@ -211,6 +221,14 @@ function computeMappingWithOverrides(
|
||||
}
|
||||
}
|
||||
|
||||
if (preferLowest) {
|
||||
candidates = [...candidates].sort((a, b) => {
|
||||
const ra = a.remain >= 0 ? a.remain : 101;
|
||||
const rb = b.remain >= 0 ? b.remain : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
|
||||
const exactMatch = candidates.find(
|
||||
(f) =>
|
||||
f.type?.toUpperCase() === req.type?.toUpperCase() &&
|
||||
@@ -270,7 +288,8 @@ export function useMultiPrinterFilamentMapping(
|
||||
filamentReqs: { filaments: FilamentRequirement[] } | undefined,
|
||||
defaultMappings: Record<number, number>,
|
||||
perPrinterConfigs: Record<number, PerPrinterConfig>,
|
||||
setPerPrinterConfigs: React.Dispatch<React.SetStateAction<Record<number, PerPrinterConfig>>>
|
||||
setPerPrinterConfigs: React.Dispatch<React.SetStateAction<Record<number, PerPrinterConfig>>>,
|
||||
preferLowest?: boolean,
|
||||
): UseMultiPrinterFilamentMappingResult {
|
||||
// Fetch printer status for all selected printers in parallel
|
||||
const statusQueries = useQueries({
|
||||
@@ -294,7 +313,7 @@ export function useMultiPrinterFilamentMapping(
|
||||
const config = perPrinterConfigs[printerId] || DEFAULT_PRINTER_CONFIG;
|
||||
|
||||
// Compute auto mapping for this printer
|
||||
const autoMapping = computeAmsMapping(filamentReqs, printerStatus);
|
||||
const autoMapping = computeAmsMapping(filamentReqs, printerStatus, preferLowest);
|
||||
|
||||
// Determine which mappings to use:
|
||||
// If printer has override (useDefault=false), use its custom mappings
|
||||
@@ -304,13 +323,14 @@ export function useMultiPrinterFilamentMapping(
|
||||
: defaultMappings;
|
||||
|
||||
// Compute final mapping with overrides
|
||||
const finalMapping = computeMappingWithOverrides(filamentReqs, printerStatus, effectiveMappings);
|
||||
const finalMapping = computeMappingWithOverrides(filamentReqs, printerStatus, effectiveMappings, preferLowest);
|
||||
|
||||
// Compute match details
|
||||
const matchDetails = computeMatchDetails(
|
||||
filamentReqs?.filaments,
|
||||
loadedFilaments,
|
||||
effectiveMappings
|
||||
effectiveMappings,
|
||||
preferLowest,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -329,7 +349,7 @@ export function useMultiPrinterFilamentMapping(
|
||||
config,
|
||||
};
|
||||
});
|
||||
}, [selectedPrinterIds, statusQueries, printers, filamentReqs, perPrinterConfigs, defaultMappings]);
|
||||
}, [selectedPrinterIds, statusQueries, printers, filamentReqs, perPrinterConfigs, defaultMappings, preferLowest]);
|
||||
|
||||
const isLoading = statusQueries.some((q) => q.isLoading);
|
||||
|
||||
@@ -350,7 +370,7 @@ export function useMultiPrinterFilamentMapping(
|
||||
if (!result || !result.status || !filamentReqs?.filaments) return;
|
||||
|
||||
// Compute optimal mapping for this printer
|
||||
const autoMapping = computeAmsMapping(filamentReqs, result.status);
|
||||
const autoMapping = computeAmsMapping(filamentReqs, result.status, preferLowest);
|
||||
if (!autoMapping) return;
|
||||
|
||||
// Convert autoMapping array to manualMappings record
|
||||
|
||||
@@ -1419,6 +1419,8 @@ export default {
|
||||
filamentChecks: 'Filament-Prüfungen',
|
||||
disableFilamentWarnings: 'Filament-Warnungen deaktivieren',
|
||||
disableFilamentWarningsDesc: 'Keine Warnungen über unzureichendes Filament beim Drucken oder Einreihen anzeigen',
|
||||
preferLowestFilament: 'Niedrigsten Filamentrest bevorzugen',
|
||||
preferLowestFilamentDesc: 'Bei mehreren passenden Spulen die mit dem geringsten Restfilament verwenden',
|
||||
trackingModeBuiltIn: 'Integriertes Inventar',
|
||||
trackingModeBuiltInDesc: 'RFID-Erkennung und Verbrauchserfassung inklusive',
|
||||
trackingModeSpoolmanDesc: 'Externer Filament-Management-Server',
|
||||
|
||||
@@ -1420,6 +1420,8 @@ export default {
|
||||
filamentChecks: 'Filament checks',
|
||||
disableFilamentWarnings: 'Disable filament warnings',
|
||||
disableFilamentWarningsDesc: 'Don\'t show warnings about insufficient filament when printing or queueing',
|
||||
preferLowestFilament: 'Prefer lowest remaining filament',
|
||||
preferLowestFilamentDesc: 'When multiple spools match, use the one with the least filament remaining',
|
||||
trackingModeBuiltIn: 'Built-in Inventory',
|
||||
trackingModeBuiltInDesc: 'RFID auto-matching and usage tracking included',
|
||||
trackingModeSpoolmanDesc: 'External filament management server',
|
||||
|
||||
@@ -1419,6 +1419,8 @@ export default {
|
||||
filamentChecks: 'Vérifications du filament',
|
||||
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',
|
||||
preferLowestFilament: 'Préférer le filament le plus bas',
|
||||
preferLowestFilamentDesc: 'Lorsque plusieurs bobines correspondent, utiliser celle avec le moins de filament restant',
|
||||
trackingModeBuiltIn: 'Inventaire Intégré',
|
||||
trackingModeBuiltInDesc: 'Correspondance RFID et suivi de consommation inclus',
|
||||
trackingModeSpoolmanDesc: 'Serveur de gestion externe',
|
||||
|
||||
@@ -1419,6 +1419,8 @@ export default {
|
||||
filamentChecks: 'Controlli filamento',
|
||||
disableFilamentWarnings: 'Disabilita avvisi filamento',
|
||||
disableFilamentWarningsDesc: 'Non mostrare avvisi per filamento insufficiente durante la stampa o l\'accodamento',
|
||||
preferLowestFilament: 'Preferisci il filamento con meno residuo',
|
||||
preferLowestFilamentDesc: 'Quando più bobine corrispondono, usa quella con meno filamento rimanente',
|
||||
trackingModeBuiltIn: 'Inventario integrato',
|
||||
trackingModeBuiltInDesc: 'Riconoscimento RFID automatico e tracciamento dell\'uso inclusi',
|
||||
trackingModeSpoolmanDesc: 'Server esterno per la gestione del filamento',
|
||||
|
||||
@@ -1418,6 +1418,8 @@ export default {
|
||||
filamentChecks: 'フィラメントチェック',
|
||||
disableFilamentWarnings: 'フィラメント警告を無効化',
|
||||
disableFilamentWarningsDesc: '印刷またはキュー追加時にフィラメント不足の警告を表示しない',
|
||||
preferLowestFilament: '残量が少ないフィラメントを優先',
|
||||
preferLowestFilamentDesc: '複数のスプールが一致する場合、残量が最も少ないものを使用します',
|
||||
trackingModeBuiltIn: '内蔵インベントリ',
|
||||
trackingModeBuiltInDesc: 'RFID自動検出と使用量追跡を含む',
|
||||
trackingModeSpoolmanDesc: '外部フィラメント管理サーバー',
|
||||
|
||||
@@ -1419,6 +1419,8 @@ export default {
|
||||
filamentChecks: 'Verificações de filamento',
|
||||
disableFilamentWarnings: 'Desativar avisos de filamento',
|
||||
disableFilamentWarningsDesc: 'Não mostrar avisos sobre filamento insuficiente ao imprimir ou adicionar à fila',
|
||||
preferLowestFilament: 'Preferir filamento com menor resto',
|
||||
preferLowestFilamentDesc: 'Quando vários carretéis correspondem, usar o com menos filamento restante',
|
||||
trackingModeBuiltIn: 'Inventário Interno',
|
||||
trackingModeBuiltInDesc: 'Correspondência automática de RFID e rastreamento de uso incluídos',
|
||||
trackingModeSpoolmanDesc: 'Servidor de gerenciamento de filamento externo',
|
||||
|
||||
@@ -1419,6 +1419,8 @@ export default {
|
||||
filamentChecks: '耗材检查',
|
||||
disableFilamentWarnings: '禁用耗材警告',
|
||||
disableFilamentWarningsDesc: '在打印或加入队列时不显示耗材不足警告',
|
||||
preferLowestFilament: '优先使用剩余最少的耗材',
|
||||
preferLowestFilamentDesc: '当多个料盘匹配时,使用剩余耗材最少的那个',
|
||||
trackingModeBuiltIn: '内置库存',
|
||||
trackingModeBuiltInDesc: '包含 RFID 自动匹配和用量追踪',
|
||||
trackingModeSpoolmanDesc: '外部耗材管理服务器',
|
||||
|
||||
@@ -741,6 +741,7 @@ export function SettingsPage() {
|
||||
settings.ams_temp_fair !== localSettings.ams_temp_fair ||
|
||||
settings.ams_history_retention_days !== localSettings.ams_history_retention_days ||
|
||||
settings.disable_filament_warnings !== localSettings.disable_filament_warnings ||
|
||||
settings.prefer_lowest_filament !== localSettings.prefer_lowest_filament ||
|
||||
(settings.queue_drying_enabled ?? false) !== (localSettings.queue_drying_enabled ?? false) ||
|
||||
(settings.queue_drying_block ?? false) !== (localSettings.queue_drying_block ?? false) ||
|
||||
(settings.ambient_drying_enabled ?? false) !== (localSettings.ambient_drying_enabled ?? false) ||
|
||||
@@ -816,6 +817,7 @@ export function SettingsPage() {
|
||||
ams_temp_fair: localSettings.ams_temp_fair,
|
||||
ams_history_retention_days: localSettings.ams_history_retention_days,
|
||||
disable_filament_warnings: localSettings.disable_filament_warnings,
|
||||
prefer_lowest_filament: localSettings.prefer_lowest_filament,
|
||||
queue_drying_enabled: localSettings.queue_drying_enabled,
|
||||
queue_drying_block: localSettings.queue_drying_block,
|
||||
ambient_drying_enabled: localSettings.ambient_drying_enabled,
|
||||
@@ -3610,6 +3612,23 @@ export function SettingsPage() {
|
||||
<div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-white">{t('settings.preferLowestFilament')}</p>
|
||||
<p className="text-sm text-bambu-gray">
|
||||
{t('settings.preferLowestFilamentDesc')}
|
||||
</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={localSettings.prefer_lowest_filament}
|
||||
onChange={(e) => updateSetting('prefer_lowest_filament', e.target.checked)}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-bambu-dark-tertiary peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-bambu-green"></div>
|
||||
</label>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -203,10 +203,19 @@ export function isPlaceholderDate(scheduledTime: string | null | undefined): boo
|
||||
*/
|
||||
export function autoMatchFilament(
|
||||
req: { type?: string; color?: string; nozzle_id?: number | null },
|
||||
loadedFilaments: { globalTrayId: number; type?: string; color?: string; extruderId?: number }[],
|
||||
loadedFilaments: { globalTrayId: number; type?: string; color?: string; extruderId?: number; remain?: number }[],
|
||||
usedTrayIds: Set<number>,
|
||||
preferLowest?: boolean,
|
||||
): typeof loadedFilaments[number] | undefined {
|
||||
const nozzleFilaments = filterFilamentsByNozzle(loadedFilaments, req.nozzle_id);
|
||||
let nozzleFilaments = filterFilamentsByNozzle(loadedFilaments, req.nozzle_id);
|
||||
|
||||
if (preferLowest) {
|
||||
nozzleFilaments = [...nozzleFilaments].sort((a, b) => {
|
||||
const ra = (a.remain ?? -1) >= 0 ? (a.remain ?? -1) : 101;
|
||||
const rb = (b.remain ?? -1) >= 0 ? (b.remain ?? -1) : 101;
|
||||
return ra - rb;
|
||||
});
|
||||
}
|
||||
|
||||
const exactMatch = nozzleFilaments.find(
|
||||
(f) =>
|
||||
|
||||
Reference in New Issue
Block a user