Skip to content

URGENT: QIDI Box - never start a print on the printer's stale slot mapping - #643

Merged
aceRage merged 6 commits into
mainfrom
fix/qidi-send-requires-mapping
Oct 20, 2026
Merged

aceRage merged 6 commits into
mainfrom
fix/qidi-send-requires-mapping

Conversation

@aceRage

@aceRage aceRage commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

URGENT safety fix. On 2.4.6.2, a QIDI printer with a QIDI Box was started on a plate sliced as PLA (PLA and ASA in the Box) with no Box mapping sent, and printed the ASA at PLA settings.

Root cause

The QIDI Box firmware feeds T<n> from the slot named by its saved variable value_t<n>. Only the sending slicer sets that variable. #611 sent SAVE_VARIABLE enable_box / value_t<n> from exactly one place: PrintHostSendDialog::qidi_start_script() (PrintHostDialogs.cpp), which is empty unless m_device_status.qidi is true. The printer then reuses whatever its previous print left in value_t<n>. These paths start a print with no mapping:

  1. No dialog at all: hub / phone app / Stream tab send and archive reprint. RemoteSend::prepare_host (RemoteSend.cpp ~644-720) and prepare_from_record (printhost branch, ~1287-1351) set post_action = StartPrint, and run_host (~1582) calls host->upload() directly. That upload is Octo/Moonraker print=true. There is no probe and no Box script, and nothing on the desktop pops up. This matches "the pre-send dialog never popped up".
  2. Desktop dialog, probe failed (address down, HTTP 404, timeout, or a non-Moonraker reply). refresh_device_status() gets qidi = false. The dialog is then the plain one, with no Box rows and no QIDI options, and "Upload and Print" is enabled. The owner reproduced this on an X-Max 4 preset.
  3. Desktop dialog, no device row. build_device_ui() returns early when m_devices.empty() (PrintHostDialogs.cpp ~553). That happens with a preset print_host and the device deleted from the list (migrate never re-imports it). Nothing is probed.
  4. Desktop dialog, a filament left "As sliced". No value_t<n> is sent for it, so it still loads from the stale slot.
  5. "Print all": plate_filaments_for_send only marked the current plate's filaments as used.

The sidebar Print / Send buttons, print plate and print all all go through send_gcode_legacy, so they always show the dialog. There is no "don't show again" or remembered post-action for print hosts. QIDI profiles have no host_type, so they default to octoprint (the OctoPrint class talking to Moonraker), which is probed.

What the owner's logs and screenshot show (PLA calibration fed from the ASA slot)

  • The dialog probe timed out at 3 s on a real QIDI on the LAN ("Status: unknown ... Operation timed out after 3000 milliseconds"). With that timeout there are no Box rows and no options.
  • In 2026-10-19-18-38-46.log.0:
    • At 20:51 the QIDI options script timed out after 60 s and the send stopped, which is correct.
    • At 20:54 the options script succeeded, so m_device_status.qidi was true, and the print started. Only the bed-leveling call failed ("Klippy Write Request Error"). The "sent ... (Box mapping X)" line was info level, so the mapping that went out is not in the log. It is now logged at warning level.
  • The calibration did not use a separate path. The Calibration menu (Plater::calib_pa / calib_flowrate / calib_temp, Plater.cpp ~20160 / 20664 / 20720) only builds a normal project with new_project + add_model. Its object prints on filament 1 (T0, value_t0) and is sent with the same sidebar Print -> send_gcode_legacy -> dialog.
  • QIDIStudio 2.07 never reads multi_color_controller (the string is not in its binary). It reads the Box from save_variables (box_count, filament_slot<n>, color_slot<n>) plus the printer's config/officiall_filas_list.cfg. The value quoting VALUE="'slot%s'" and the 0-based box*4+slot numbering match ours.
  • The most likely mechanism: the Box is detected (and on), but no slot named a readable material (slots_have_filaments false), so no rows are shown. A filament can also be left "As sliced" because its type did not match. In both cases qidi_start_script sends SAVE_VARIABLE enable_box VALUE=1 alone, Moonraker answers OK, and the printer keeps value_t0 = slot<ASA> from the earlier ASA print.

Fix

  • PrintHostDevices::qidi_send_policy(QidiSendFacts) is one pure function that every path uses. Its results are NotApplicable / Start / UploadOnlyBusy / UploadOnlyNoProbe / NeedsMapping / ConfirmTypeMismatch. The helpers are qidi_box_capable_model (profile: Q2/Q2C, X-Max 4, X-Plus 4, X-Plus 5), filament_types_match and qidi_auto_mapping (material-first, as the dialog's auto-match).
  • Desktop dialog:
    • A Box-capable preset with no device list gets its own address as the device (as Elegoo Link already does).
    • "Upload and Print" asks the policy:
      • Unreadable Box (probe failed, or the printer answered without multi_color_controller): Upload only (the default and the auto-answer), Retry, or an explicit "Print anyway".
      • Unmapped used filament: the print is refused, with the list.
      • Slot holds another material (PLA vs ASA): a warning with "Change the slots" / "Print anyway".
    • The note under the printer says "Couldn't read the QIDI Box: the printer would use its last saved slot mapping". The QIDI options are not offered blind, because they need the printer to answer and the queue's idle check would stop the send anyway.
    • The mapping is read through mapping_choice_positions(), so it also works with the Bambu-style swatch map for third-party send dialogs (CANVAS, U1, QIDI Box) #625/Happy Hare and AFC slot reading for the Moonraker send dialog #612 swatch panel.
  • Hub / phone / Stream tab / archive reprint (arm_qidi_guard + qidi_guard_start):
    • Applies to any Moonraker-speaking print-host target, or a Box-capable model.
    • Probes the printer and auto-maps by material.
    • If the policy says Start, it sends the Box script (enable_box + value_t<n>) before the upload. Otherwise it refuses with the reason and nothing is sent.
  • "Print all" counts every plate's filaments.
  • Reading the Box:
    • The probe waits 10 s for a Box-capable model (3 s for everything else).
    • When multi_color_controller names no materials, the probe reads the Box the QIDIStudio way (save_variables + officiall_filas_list.cfg).
    • enable_box / box_count in save_variables also mark QIDI firmware with a Box.
  • Box default: an empty or unreadable Box defaults to on. Printing from the toolhead spool (enable_box 0) feeds whatever is in the toolhead, so it is only ever an explicit untick.
  • Read-back: after the Box script, the desktop queue (PrintHost.cpp) and the hub send read save_variables back. If enable_box and every value_t<n> do not match what was sent, they refuse to upload.
  • Material mismatch confirmation for every host whose slots name materials (the CANVAS dialog included). A PLA filament going to an ASA slot asks "Change the slots" / "Print anyway".

Other hosts: unchanged. The exceptions are one extra read-only probe on a remote print to an octoprint-type host, and "print all" marking the filaments of every plate.

Same hole elsewhere (reported, not fixed)

  • Elegoo CANVAS remote send: prepare_host sends no extended_info, so no slot_map. The printer gets the start without a mapping, exactly as before the CANVAS work. This is a different firmware: there is no saved stale variable, but the result is still unmapped. It needs a CANVAS read in run_host.

  • U1: no hole. The remote path is the LAN snapmaker kind, with auto_match plus start_print_mapped, and the desktop uses the connect page.

  • Device tab file list "Print" (RemoteFileGridModel::StartPrint -> MoonrakerFiles::start_print) on a QIDI: it starts a stored file on the stale mapping, the same as the printer's own touchscreen. Follow-up.

  • Flashforge IFS dialog: it has its own slot dialog and does not use these rows, so it is not covered by the mismatch confirmation.

Unverified on hardware (nobody here has a QIDI Box)

  • The officiall_filas_list.cfg layout ([fila<id>] type =, [colordict] <id> = #RRGGBB) is inferred from the property-tree keys in the QIDIStudio binary.
  • So is the assumption that a slot with filament_slot<n> > 0 is loaded.
  • If either guess is wrong, the slots read as unknown and the print is refused (NeedsMapping). A wrong slot is never started, because the read-back confirms what the printer saved.

Compatibility with #625 / #612

The guard reads mapping_choice_positions() and m_mapping_filament, which the swatch panel answers. Expect textual conflicts in PrintHostDialogs.cpp: update_mapping_note (#625 F14 removed its early return), rebuild_mapping_rows, and the QIDI note in refresh_device_status. #616's async probe would need confirm_qidi_start to run only after the probe finishes. #620 touches init() (the Upload and Print handler).

Tests

tests/slic3rutils/qidi_send_policy_tests.cpp covers the policy matrix, including "profile says QIDI Box, probe failed" and the owner's 404 repro. It also covers the reported PLA/ASA plate, PETG not in the Box, busy, Box off or absent, and the U1 not being affected.

Hand test (owner, no QIDI): with a QIDI X-Max 4 preset and a dead address, check that the note shows, that Upload is the default, and that Upload and Print shows Upload only / Retry / Print anyway.

The QIDI Box firmware feeds T<n> from the slot its saved variable
value_t<n> names, and only the sending slicer sets it. #611 sent the
mapping only from the desktop send dialog, and only when a device was
picked and its 3 s probe answered. Every other start went out with no
mapping, so the printer reused its last print's slots: a PLA plate fed
the ASA in the Box, at PLA settings.

Holes closed:
- hub / phone / Stream tab send and archive reprint (RemoteSend
  prepare_host / prepare_from_record -> run_host) uploaded with
  print=true and no Box script at all, with no dialog anywhere. run_host
  now reads the printer, maps every used filament material-first and
  sends the Box script before the upload, or refuses with the reason.
- desktop dialog with no device row (device list empty: build_device_ui
  returned early, nothing probed): a Box-capable QIDI preset now gets its
  own address as the device, like Elegoo Link.
- desktop dialog after a failed probe / with filaments left "As sliced":
  "Upload and Print" now asks qidi_send_policy. Unreadable Box: Upload
  only (default), Retry, or an explicit Print anyway. Unmapped filament:
  refused with the list. Slot of another material: warn and confirm.
- "Print all" counted only the current plate's filaments.

The decision is one pure function, PrintHostDevices::qidi_send_policy,
with qidi_auto_mapping / qidi_box_capable_model / filament_types_match,
covered by tests/slic3rutils/qidi_send_policy_tests.cpp.

Deft
…s unreadable

Owner repro: Qidi X-Max 4 preset, physical printer at an address with
no printer behind it, probe answered HTTP 404 -> plain dialog, no QIDI
options, Upload and Print enabled. The profile's printer_model now
decides too: a Box-capable model whose printer failed the probe, or
answered without multi_color_controller, is UploadOnlyNoProbe. The
dialog says so in words ("Couldn't read the QIDI Box: the printer would
use its last saved slot mapping"), Upload stays the default, and Upload
and Print asks (Upload only / Retry / Print anyway). The QIDI options
are not offered blind: they need the printer to answer.

Deft
So the swatch panel of #625/#612, which answers mapping_choice_positions()
instead of filling wxChoice rows, is checked the same way once merged.

Deft
…longer probe

From the owner's 2.4.6.2 logs and screenshot (PLA calibration fed from the
ASA slot):
- The dialog probe timed out at 3 s on a real QIDI on the LAN, so no Box
  rows and no options. A Box-capable model now waits 10 s.
- QIDIStudio 2.07 does not read multi_color_controller at all: it reads
  save_variables (box_count, filament_slot<n>, color_slot<n>) and the
  printer's config/officiall_filas_list.cfg. When multi_color_controller
  names no materials the probe now reads those, and save_variables'
  enable_box / box_count also mark QIDI firmware with a Box.
- The likely mechanism of the wrong slot: with the Box on and no rows
  (nothing readable) or a filament left "As sliced", qidi_start_script sent
  "enable_box VALUE=1" alone and the printer kept value_t0 from the last
  (ASA) print. qidi_send_policy now refuses that, an empty or unreadable
  Box defaults to on (never silently to the toolhead spool), and after the
  script both the desktop queue and the hub send read save_variables back
  and refuse to upload unless enable_box and every value_t<n> match.
- The "sent" lines are logged at warning level, so the mapping is in the
  user's log next time.
- Material mismatch (PLA filament -> ASA slot) asks before printing for
  every host whose slots name materials (CANVAS dialog included).

Deft
…ons unread

Owner hand test (X-Max 4 preset, HTTP 404):
- The notes under the printer row wrapped at CONTENT_WIDTH * em_unit,
  some 2400 px, so the new QIDI note stretched the dialog to the screen.
  They now wrap at FromDIP(460), and the note is shorter.
- A Box-capable QIDI that did not answer still shows Use QIDI Box (on),
  Auto bed leveling, Timelapse and, where QIDIStudio's profiles have it,
  Polar cooler. They are remembered as before and go out at send time
  only if the printer answers then; the queue reads its state first and
  stops the send with a message when it does not.

Deft
@aceRage
aceRage marked this pull request as ready for review October 20, 2026 22:19
@aceRage
aceRage merged commit 205b0a9 into main Oct 20, 2026
aceRage added a commit that referenced this pull request Oct 20, 2026
aceRage added a commit that referenced this pull request Oct 20, 2026
update_mapping_note() keeps the no-rows guard and adds #643's QIDI Box
problem lines. On a QIDI Box those lines replace the swatch map's own
type/colour mismatch lines, so a wrong-material slot is reported once.

Deft
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant