fix(print-modal): allow cross-extruder AMS slot picks on dual-nozzle (#1722)

Before this change, the Re-print and Schedule modals' per-filament
  slot dropdown hid every slot whose extruder didn't match the filament's
  slicer-assigned nozzle. On H2D with AMS A+C on the left and B on the
  right, an L-assigned filament could only pick A or C and an R-assigned
  one could only pick B — locking the user out of cross-extruder picks
  even when they'd intentionally loaded the required filament into the
  other AMS.

  The slicer wasn't the source of the asymmetry: BambuStudio Desktop,
  OrcaSlicer Desktop, and the Bambuddy sidecar all produced identical
  filament_map values for the same source 3MF. Bambuddy's UI filter was.

  Drop the f.extruderId === item.nozzle_id clause in FilamentMapping's
  loadedFilaments filter. Single-nozzle and FTS short-circuits stay; the
  L/R row badge stays as a hint to the slicer's intent. Printer firmware
  decides at start-print whether the cross-extruder ams_mapping is valid.
This commit is contained in:
maziggy
2026-06-12 13:12:20 +02:00
parent f2a3917e90
commit 61df0b68d2
5 changed files with 31 additions and 16 deletions
+1
View File
File diff suppressed because one or more lines are too long
@@ -193,7 +193,15 @@ describe('FilamentMapping — FTS routing', () => {
expect(screen.queryByLabelText(/Force color match/i)).not.toBeInTheDocument();
});
it('still applies the per-nozzle filter when FTS is null', async () => {
it('offers cross-extruder slots in the dropdown without FTS (#1722)', async () => {
// Before #1722 the dropdown filtered to only slots whose extruder matched
// the filament's slicer-assigned nozzle. On a dual-nozzle printer with one
// AMS per side, that prevented the user from picking a slot on the OTHER
// extruder even when they'd intentionally loaded the required filament
// there. The fix: trust the user, show every loaded slot regardless of
// which extruder it's wired to. The L/R badge on the filament row still
// tells the user what the slicer planned; the printer firmware accepts
// or rejects the cross-extruder ams_mapping at start-print.
server.use(
http.get(
'/api/v1/printers/:id/status',
@@ -219,13 +227,13 @@ describe('FilamentMapping — FTS routing', () => {
/>,
);
// Required nozzle is 1 (LEFT) but AMS 0 is on extruder 0 (RIGHT) — neither
// slot should appear in the dropdown.
// Required nozzle is 1 (LEFT) and AMS 0 is wired to extruder 0 (RIGHT).
// Both slots must STILL appear so the user can pick them — explicitly the
// cross-extruder scenario the #1722 fix unblocks.
await waitFor(() => {
// Wait for component to render — the slot label should NOT be present
expect(screen.queryByText(/Bambu PLA/)).not.toBeInTheDocument();
expect(screen.queryByText(/Bambu PETG/)).not.toBeInTheDocument();
expect(screen.getByText(/Bambu PLA/)).toBeInTheDocument();
});
expect(screen.getByText(/Bambu PETG/)).toBeInTheDocument();
});
it('renders sub-brand + material-disambiguated colour on the required side (#1718)', async () => {
@@ -245,14 +245,20 @@ export function FilamentMapping({
<option value="" className="bg-bambu-dark text-bambu-gray">
-- Select slot --
</option>
{loadedFilaments
.filter(
(f) =>
item.nozzle_id == null ||
ftsInstalled ||
f.extruderId === item.nozzle_id,
)
.map((f) => {
{/*
#1722: every loaded slot is offered for every filament row,
regardless of which extruder the slot is wired to. Before this
change a slot was only listed when its extruder matched the
filament's slicer-assigned nozzle (item.nozzle_id), which
locked users out of cross-extruder picks even when they'd
intentionally loaded the required filament into the "other"
AMS. The L/R badge on the filament row still tells the user
what the slicer planned; the dropdown now trusts the user to
pick based on their physical setup. Printer firmware accepts
or rejects the ams_mapping at start-print — failure is loud,
not silent.
*/}
{loadedFilaments.map((f) => {
const remainingWeight = trayRemainingWeightMap.get(f.globalTrayId);
const remainingLabel = remainingWeight != null
? t('printModal.slotRemainingShort', {
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -26,7 +26,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-C-AWhT3K.js"></script>
<script type="module" crossorigin src="/assets/index-I24jNRag.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-45eedLWT.css">
</head>
<body>