Files
OrcaSlicer/docs/HLSD/multi-extruder-ams.md
T
2026-10-07 19:05:06 +08:00

11 KiB

Multi-extruder and multi-AMS device control

DeviceManager/MachineObject model the toolheads, AMS units, and filament slots of a connected printer and drive the device-control UI from that model. The model represents the counts as device-reported runtime values rather than compile-time caps, so a printer with an arbitrary number of toolheads, several AMS units, and a non-uniform number of slots per unit is handled by the same code paths. The printer agent is responsible for translating its printer's state into the shared JSON dialect this model consumes; the agent boundary is described in printer-agent.md, and this document describes the dialect and the model.

Model layout

MachineObject owns the device subsystems that this document covers:

  • DevExtderSystem — the toolheads (extruders) and their per-toolhead filament/temperature state.
  • DevFilaSystem — the AMS units (DevAms) and their trays (DevAmsTray).
  • DevNozzleSystem — the nozzles and, on rack printers, DevNozzleRack.
  • DevFilaSwitch — a filament switch that can feed several AMS units to more than one extruder.

Each subsystem exposes its own count — DevExtderSystem::GetTotalExtderCount(), DevFilaSystem::GetAmsCount(), DevAms::GetSlotCount() — and the rest of the application derives its layout from those values rather than from fixed assumptions.

Wire dialect

The dialect is the Bambu-shaped JSON that DeviceManager and MachineObject already consume. An agent whose printer speaks something else is expected to translate into it; the agent is not free to invent a new shape. Two representations coexist for extruders, and one for AMS/slots.

Extruders

The generic representation is print.device.extruder, parsed by ExtderSystemParser::ParseV2_0 (DevExtruderSystem.cpp). It has a packed state integer and an info array with one object per toolhead:

device.extruder.state           packed integer
  bits  0..3   total extruder count
  bits  4..7   current extruder id
  bits  8..11  target extruder id
  bits 12..14  switch state
  bits 15..18  currently loading extruder id
  bit   19     busy for loading

device.extruder.info[]          one entry per toolhead
  id          extruder id
  filam_bak   array of backup-group bits
  info        bit 1 has filament, bit 2 buffer has filament, bit 3 nozzle present
  temp        bits 0..15 current temperature, bits 16..31 target temperature
  spre        slot_id bits 0..7, ams_id bits 8..15 (previous)
  snow        same packing (current)
  star        same packing (target)
  stat        bit 0..15 AMS status, bits 16..31 RFID status
  hnow        current nozzle id

The legacy single/dual representation is the pair of scalar fields nozzle_temper / nozzle_target_temper, plus the print.ams.tray_tar / tray_now tray pointer, parsed by ExtderSystemParser::ParseV1_0. That parser is a fallback and only fills the main extruder when the reported toolhead count is one.

Physical extruder ids are fixed (MAIN_EXTRUDER_ID = 0, DEPUTY_EXTRUDER_ID = 1). Bambu printers present the two toolheads as a left/right pair and invert the physical-to-logical mapping; generic printers use the toolhead id itself as the logical id. DevNozzle::GetLogicExtruderId() / GetExtruderId() centralise that distinction.

AMS units and slots

AMS units arrive as print.ams.ams[] and are parsed by DevFilaSystemParser::ParseV1_0. Each unit carries an id and an info bitfield:

info bits  0..3    type: 0 dummy, 1 AMS, 2 AMS-Lite, 3 N3F, 4 N3S, 5 AMS-Lite-mixed (N9)
info bits  8..11   bound extruder id (0xE marks a switch-bound unit)
info bits 24..27   switch inlet when the unit is switch-bound

DevAmsType (DevDefs.h) enumerates the resulting unit kinds: EXT_SPOOL, AMS, AMS_LITE, N3F, N3S, and the AMS_LITE_MIXED N9 variant. AMS_LITE_MIXED reports itself as AMS_LITE to generic callers; code that needs the distinction uses IsAmsLiteMixed().

A unit whose bound-extruder nibble is 0xE is switch-bound: it is kept only when a DevFilaSwitch is installed, in which case its extruder id is pinned to the main extruder and its binding set becomes {0, 1}; otherwise the unit is dropped from the AMS list.

Each unit owns a map of DevAmsTray keyed by the tray id from the payload. The slot count is a property of the unit kind and the reported trays:

  • AMS, AMS_LITE, and N3F report four slots on Bambu printers, matching the firmware contract;
  • N3S reports one slot;
  • EXT_SPOOL and any non-Bambu unit report the number of trays actually present.

The tray ids are the payload's slot indices; they are expected to be unique, 0-based, and contiguous within a unit, because the generic tray-index mapping below is cumulative.

Bulk/extended spools are carried by print.vir_slot, which DeviceManager parses into MachineObject::vt_slot. Two ids are fixed: VIRTUAL_TRAY_MAIN_ID = 255 (extruder 0) and VIRTUAL_TRAY_DEPUTY_ID = 254 (extruder 1); the id convention is vt_id = 255 - extruder_id, so further extruders use 253, 252, … . The vir_slot parser recognises the 255/254 pair; the agent filament descriptor path DevFilaSystemParser::ParseAgentFilament is the entry that stores a fuller descending set in vt_slot.

Mapping

Several subsystems relate AMS units, trays, and extruders, and more than one place derives a tray index. The Bambu mapping path (DevMappingUtil) computes ams_id * 4 + slot_id and knows only the Bambu unit kinds; DevFilaSystem provides the device model's own mapping below. The two agree for Bambu units and diverge for a non-Bambu unit whose slot count is not four.

AMS id to extruder id

DevFilaSystem::GetExtruderIdByAmsId() resolves an AMS id to the extruder it feeds:

  • an AMS unit returns its bound extruder (DevAms::GetExtruderId());
  • a virtual tray returns VIRTUAL_TRAY_MAIN_ID - vt_id;
  • Bambu's virtual AMS ids map to the main/deputy extruder.

A unit may be bound to more than one extruder when a DevFilaSwitch feeds it; the binding set (DevAms::m_binded_extruder_set) and switch inlet are read by switch-aware code while the single-extruder id stays pinned for legacy callers.

Tray index

DevFilaSystem::GetTrayIndexMap() maps a global tray index to an (ams_id, slot_id) pair and is used for backup-slot translation, calibration addressing, and the mapping popups.

  • Bambu AMS: ams_id * 4 + slot_id; N3S: the AMS id; AMS-Lite-mixed (N9): 24 + slot_id.
  • Generic (non-Bambu): a running lane index — the count of trays in preceding units plus the slot id. This depends on each unit's tray ids being 0-based and contiguous.

Virtual-slot recognition

devPrinterUtil::IsVirtualSlot() decides whether an id denotes a virtual tray. On Bambu printers it is the fixed pair {255, 254}. On generic printers it is the descending range [255 - count + 1, 255], where count is the selected printer preset's extruder count. The preset is expected to match the connected device; when the two disagree the virtual range is wrong, which is why a mismatch is reported rather than silently accepted.

Device-control UI

The device-control widgets follow the same device-reported counts.

  • AMSModel (AMSItem.hpp) mirrors DevAmsType numerically: EXT_AMS, GENERIC_AMS, AMS_LITE, N3F_AMS, N3S_AMS. A plain AMS maps to GENERIC_AMS.
  • On non-Bambu printers use_generic_ams_layout() routes GENERIC_AMS units through a variable-lane rendering that draws one lane per reported tray; Bambu keeps its fixed four-slot rendering.
  • AMSControl picks its layout by vendor: Bambu keeps the left/right presentation (CreateAmsSingleNozzle for one toolhead, CreateAmsDoubleNozzle for two), while non-Bambu printers use CreateAmsMultiNozzle, which builds one preview and one AMS simplebook per toolhead for any count.
  • The status panel's ExtruderImage renders one per-toolhead state per nozzle and is resized to GetTotalExtderCount(). Non-Bambu printers choose the active toolhead through m_generic_nozzle_selector (shown when more than one toolhead exists); Bambu keeps its left/right m_nozzle_btn_panel. Nozzle temperature controls are created on demand per toolhead.

Constraints

  • Single dialect. The agent translates its protocol into this JSON; the model does not learn vendor protocols. Extending the model with a new unit kind or field means extending the dialect.
  • Agent-produced counts. Toolhead, AMS, and slot counts come from the device payload. Nothing caps them at two toolheads or four slots except the Bambu contract described above.
  • Extruder dialect invariants. device.extruder.state's count field must equal the length of info[], and info[] must be ordered by extruder id with id equal to the array index: the parser places toolheads by array position while GetExtderById() indexes by id.
  • Preset/device agreement. The generic layout and virtual-slot range derive their count from the selected printer preset. A printer preset must therefore declare the same number of nozzle_diameter entries as the device has toolheads; a mismatch is surfaced because the sidebar and extruder logic would otherwise disagree.
  • Bambu compatibility. Bambu-specific presentation (left/right pair, fixed 4-slot units, fixed virtual ids, physical/logical inversion) is preserved behind the vendor check. Generic paths do not alter it.
  • Tray-index invariant. The generic tray-index map is cumulative, so payload tray ids must be unique, 0-based, and contiguous per unit. A malformed id is a producer error.
  • Protocol markers. The dialect has no neutral equivalent for some Bambu state, so agents that lack it send empty print.cfg, print.fun, print.aux, and print.stat. Their presence selects the new-protocol parse path; they are compatibility markers, not feature switches.

Main implementation locations

  • DevExtruderSystem — toolhead model and the device.extruder parser
  • DevFilaSystem — AMS/tray model, the AMS parser, and the mapping helpers
  • DevDefs.h — DevAmsType, extruder ids, and virtual tray ids
  • DeviceManager.cpp — message dispatch, vir_slot parsing, and device state assembly
  • AMSControl.cpp — per-toolhead AMS panes (CreateAmsMultiNozzle) and the generic layout
  • AMSItem.hpp — AMSModel and the variable-lane AMS rendering
  • StatusPanel.cpp — ExtruderImage, the toolhead selector, and per-toolhead temperature controls
  • MoonrakerPrinterAgent.cpp — producer example (AMS payload and the device.extruder encoding)