Nozzle-aware AMS mapping for dual-nozzle printers, BL spool detection fix, AMS startup fix, SQLite WAL (#318)

Dual-nozzle H2D/H2D Pro: filament matching now respects nozzle assignments
from the 3MF file. Each AMS unit feeds a specific nozzle (L/R), and the
scheduler/frontend constrain matching to only trays on the correct nozzle.
Falls back to unfiltered matching when no trays exist on the target nozzle.
L/R badges shown in the filament mapping UI. Translated in en/de/ja/it.

Fix AMS slot config overwritten on startup: on_ams_change unconditionally
unlinked BL spool assignments on every MQTT pushall, then re-assigned them
sending ams_filament_setting without setting_id — clearing the printer's
filament preset. Now compares spool RFID identifiers before unlinking.

Fix BL spool detection false positives: removed tray_info_idx from detection
logic in both backend is_bambu_lab_spool() and frontend isBambuLabSpool().
Third-party spools using Bambu generic presets had GF-prefixed tray_info_idx
values, causing misidentification. Now uses only tray_uuid and tag_uid.

SQLite WAL mode with 5s busy timeout reduces "database is locked" errors.
This commit is contained in:
maziggy
2026-02-13 11:48:32 +01:00
parent ee2af79f4a
commit f9b47282a1
26 changed files with 1105 additions and 515 deletions
@@ -124,26 +124,32 @@ function computeMatchDetails(
}
}
// Auto-match
const exactMatch = loadedFilaments.find(
// Auto-match with nozzle-aware filtering
let candidates = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId));
if (req.nozzle_id != null) {
const nozzleFiltered = candidates.filter((f) => f.extruderId === req.nozzle_id);
if (nozzleFiltered.length > 0) {
candidates = nozzleFiltered;
}
}
const exactMatch = candidates.find(
(f) =>
!usedTrayIds.has(f.globalTrayId) &&
f.type?.toUpperCase() === req.type?.toUpperCase() &&
normalizeColorForCompare(f.color) === normalizeColorForCompare(req.color)
);
const similarMatch = exactMatch
? undefined
: loadedFilaments.find(
: candidates.find(
(f) =>
!usedTrayIds.has(f.globalTrayId) &&
f.type?.toUpperCase() === req.type?.toUpperCase() &&
colorsAreSimilar(f.color, req.color)
);
const typeOnlyMatch =
exactMatch || similarMatch
? undefined
: loadedFilaments.find(
(f) => !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase()
: candidates.find(
(f) => f.type?.toUpperCase() === req.type?.toUpperCase()
);
const loaded = exactMatch ?? similarMatch ?? typeOnlyMatch;
@@ -196,26 +202,32 @@ function computeMappingWithOverrides(
continue;
}
// Auto-match
const exactMatch = loadedFilaments.find(
// Auto-match with nozzle-aware filtering
let candidates = loadedFilaments.filter((f) => !usedTrayIds.has(f.globalTrayId));
if (req.nozzle_id != null) {
const nozzleFiltered = candidates.filter((f) => f.extruderId === req.nozzle_id);
if (nozzleFiltered.length > 0) {
candidates = nozzleFiltered;
}
}
const exactMatch = candidates.find(
(f) =>
!usedTrayIds.has(f.globalTrayId) &&
f.type?.toUpperCase() === req.type?.toUpperCase() &&
normalizeColorForCompare(f.color) === normalizeColorForCompare(req.color)
);
const similarMatch = exactMatch
? undefined
: loadedFilaments.find(
: candidates.find(
(f) =>
!usedTrayIds.has(f.globalTrayId) &&
f.type?.toUpperCase() === req.type?.toUpperCase() &&
colorsAreSimilar(f.color, req.color)
);
const typeOnlyMatch =
exactMatch || similarMatch
? undefined
: loadedFilaments.find(
(f) => !usedTrayIds.has(f.globalTrayId) && f.type?.toUpperCase() === req.type?.toUpperCase()
: candidates.find(
(f) => f.type?.toUpperCase() === req.type?.toUpperCase()
);
const loaded = exactMatch ?? similarMatch ?? typeOnlyMatch;