bambutools
bambutools contains a collection of methods used as tools (mostly internal).
Classes:
| Name | Description |
|---|---|
AMSControlCommand |
AMS Control Commands enum. The lowercase name is the |
AMSDryerFanStatus |
AMS drying fan status extracted from bits 18-21 of ams_info. |
AMSDryerRefusal |
Why an AMS dryer cannot start now, from each AMS block's |
AMSDryerSubStatus |
AMS drying sub-status extracted from bits 22-25 of ams_info. |
AMSHeatingState |
AMS Drying/Heater states extracted from bits 4-7 of ams_info. |
AMSModel |
AMS model enum |
AMSSeries |
AMS Series enum |
AMSUserSetting |
AMS User Settings enum |
ActiveTool |
The currently active extruder index. |
AirConditioningMode |
The mode the printer's air conditioning sub system is in. |
DetectorSensitivity |
Sensitivity level for X-Cam AI vision detectors (spaghetti, pile-up, clump, air-print). |
ExtensionToolMountState |
Toolhead extension-tool mount state reported in |
ExtensionToolType |
Toolhead extension-tool type reported in |
ExtruderInfoState |
Consolidated logical states for extruder sensor status. |
ExtruderStatus |
Operational states for physical extruders. |
FilamentStep |
The step of a filament load or unload, named as Bambu Studio's |
FilamentStepType |
The kind of filament change under way, as Bambu Studio's |
HMSAction |
The dialog buttons Bambu Studio knows, by the ids its HMS action table uses |
NozzleDiameter |
Nozzle Diameter enum |
NozzleFlowType |
Canonical nozzle flow families used by BambuStudio/Orca nozzle identifiers. |
NozzleMaterialCode |
Canonical material codes used in encoded nozzle identifiers. |
NozzleType |
Canonical cross-model nozzle material type from telemetry. |
PlateType |
Used by |
PrintOption |
Print Option enum |
PrinterModel |
Printer model enum |
PrinterSeries |
Printer Series enum |
ServiceState |
This enum is used by |
SpeedLevel |
Print speed profile levels. Maps human-readable names to the firmware integer codes |
TrayState |
Operational status of the filament tray. |
Functions:
| Name | Description |
|---|---|
build_nozzle_identifier |
Build a canonical Bambu-style nozzle identifier. |
cache_delete |
Remove |
cache_read |
Load a persisted value from |
cache_write |
Persist value to |
decodeError |
Decodes a raw print_error integer into a full HMS dictionary. |
decodeHMS |
Decodes the raw HMS list from telemetry into a structured list of dictionaries. |
dryerRefusalMessage |
Explains why a dryer cannot start, the way BambuStudio does |
getAMSHeatingState |
Decodes the AMS drying/heater state from bits 4-7 of the ams_info value. |
getAMSModelBySerial |
Returns the hardware model based on the serial number prefix. |
getAMSSeriesByModel |
Returns the AMS series enum based on the provided model. |
getPrinterModelBySerial |
Returns the Printer model enum based on the provided serial #. |
getPrinterSeriesByModel |
Returns the Printer series enum based on the provided model. |
get_file_md5 |
Generates an MD5 hex hash for a given file. |
hmsActions |
The dialog buttons Bambu Studio shows for |
jsonSerializer |
Module-level JSON serializer for use with |
make_cache_key |
Sanitize a raw string into a filesystem-safe cache key. |
nozzle_type_to_telemetry |
Convert canonical |
parseAMSInfo |
Extracts all documented telemetry attributes from the AMS info hex string. |
parseAMSStatus |
Maps the ams_status code to human-readable descriptions. |
parseExtruderInfo |
Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names. |
parseExtruderStatus |
Decodes the operational extruder state using the exhaustive Enum map. |
parseExtruderTrayState |
Decodes an extruder's tray from its |
parseFilamentStep |
Names the filament load or unload step, as Bambu Studio's |
parseRFIDStatus |
Can be used to parse |
parseStage |
Maps stg_cur numeric codes to human-readable print stages. |
parse_nozzle_identifier |
Parse a Bambu-style nozzle identifier into flow family, material, and diameter. |
parse_nozzle_type |
Resolve a nozzle type from cross-model telemetry strings or encoded IDs. |
printer_cache_dir |
One printer's own cache folder: |
resolve_external_spool_trays |
Name the external spool holder that feeds each filament of a sliced plate on a |
scaleFanSpeed |
Scales proprietary 0-15 fan speed values to a 0-100 percentage. |
sortFileTreeAlphabetically |
Sorts a dict of file/directory nodes hierarchically. |
unpackTemperature |
Unpacks a 32-bit packed temperature integer into a tuple of (Actual, Target). |
Attributes:
| Name | Type | Description |
|---|---|---|
FILAMENT_STEP_TEXT |
Bambu Studio's step labels ( |
|
HMS_ACTION_COMMAND |
What bpm sends for a button, matching Studio's |
|
HMS_ACTION_LABEL |
Bambu Studio's button text ( |
|
HMS_LIVEVIEW_CODES |
Errors Bambu Studio adds a View Liveview button to ( |
|
HMS_RETRY_CODES |
Errors Bambu Studio answers with Retry and Confirm instead of its table's buttons |
|
VIRTUAL_TRAY_DEPUTY_ID |
Virtual tray id of the deputy (left on H2D) extruder's external spool holder. |
|
VIRTUAL_TRAY_MAIN_ID |
Virtual tray id of the main (right on H2D) extruder's external spool holder. |
FILAMENT_STEP_TEXT
module-attribute
FILAMENT_STEP_TEXT = {
IDLE: "",
HEAT_NOZZLE: "Heat the nozzle",
CUT_FILAMENT: "Cut filament",
PULL_CURR_FILAMENT: "Pull back current filament",
PUSH_NEW_FILAMENT: "Push new filament into extruder",
PURGE_OLD_FILAMENT: "Purge old filament",
CONFIRM_EXTRUDED: "Confirm extruded",
CHECK_POSITION: "Check filament location",
}
Bambu Studio's step labels (Widgets/FilamentLoad.cpp).
HMS_ACTION_COMMAND
module-attribute
HMS_ACTION_COMMAND = {
FILAMENT_EXTRUDED: "done",
RETRY_FILAMENT_EXTRUDED: "resume",
CONTINUE: "resume",
RETRY_PROBLEM_SOLVED: "resume",
ABORT: "abort",
OK_BUTTON: "clean_print_error",
CHECK_ASSISTANT: "assistant",
CANCLE: "close",
DBL_CHECK_CANCEL: "close",
DBL_CHECK_DONE: "done",
DBL_CHECK_RETRY: "resume",
DBL_CHECK_OK: "clean_print_error",
}
What bpm sends for a button, matching Studio's DeviceErrorDialog::on_button_click:
done, resume and abort are ams_control params, clean_print_error clears the
error, assistant opens the error's url and close sends nothing. A known button not
listed here sends a command bpm does not implement, so it carries no command.
HMS_ACTION_LABEL
module-attribute
HMS_ACTION_LABEL = {
RESUME_PRINTING: "Resume Printing",
RESUME_PRINTING_DEFECTS: "Resume (defects acceptable)",
RESUME_PRINTING_PROBELM_SOLVED: "Resume (problem solved)",
STOP_PRINTING: "Stop Printing",
CHECK_ASSISTANT: "Check Assistant",
FILAMENT_EXTRUDED: "Filament Extruded, Continue",
RETRY_FILAMENT_EXTRUDED: "Not Extruded Yet, Retry",
CONTINUE: "Finished, Continue",
LOAD_VIRTUAL_TRAY: "Load Filament",
OK_BUTTON: "OK",
FILAMENT_LOAD_RESUME: "Filament Loaded, Resume",
JUMP_TO_LIVEVIEW: "View Liveview",
NO_REMINDER_NEXT_TIME: "No Reminder Next Time",
REFRESH_NOZZLE: "Recheck",
IGNORE_NO_REMINDER_NEXT_TIME: "Ignore. Don't Remind Next Time",
IGNORE_RESUME: "Ignore this and Resume",
PROBLEM_SOLVED_RESUME: "Problem Solved and Resume",
TURN_OFF_FIRE_ALARM: "Got it, Turn off the Fire Alarm.",
RETRY_PROBLEM_SOLVED: "Retry (problem solved)",
STOP_DRYING: "Stop Drying",
CANCLE: "Cancel",
REMOVE_CLOSE_BTN: "",
PROCEED: "Proceed",
OK_JUMP_RACK: "OK",
ABORT: "Abort",
DISABLE_PURIFICATION: "Disable Purification for This Print",
DONT_REMIND_NEXT_TIME: "Don't Remind Me",
DBL_CHECK_CANCEL: "Cancel",
DBL_CHECK_DONE: "Done",
DBL_CHECK_RETRY: "Retry",
DBL_CHECK_RESUME: "Resume",
DBL_CHECK_OK: "Confirm",
}
Bambu Studio's button text (DeviceErrorDialog::init_button_list).
HMS_LIVEVIEW_CODES
module-attribute
Errors Bambu Studio adds a View Liveview button to (s_jump_liveview_error_codes).
HMS_RETRY_CODES
module-attribute
HMS_RETRY_CODES = {
"0701-8004",
"0701-8005",
"0701-8007",
"0701-8012",
"0702-8012",
"0703-8012",
"07FF-8012",
"07FF-8013",
}
Errors Bambu Studio answers with Retry and Confirm instead of its table's buttons
(DeviceErrorDialog.cpp message_containing_retry).
VIRTUAL_TRAY_DEPUTY_ID
module-attribute
Virtual tray id of the deputy (left on H2D) extruder's external spool holder.
VIRTUAL_TRAY_MAIN_ID
module-attribute
Virtual tray id of the main (right on H2D) extruder's external spool holder.
AMSControlCommand
Bases: Enum
AMS Control Commands enum. The lowercase name is the ams_control param sent.
DONE: filament extruded, the load may continue (Studio's "Filament Extruded, Continue").ABORT: cancel the running load or unload (Studio's "Abort" and the load view's "Stop").
AMSDryerFanStatus
Bases: IntEnum
AMS drying fan status extracted from bits 18-21 of ams_info. Two independent fans (fan1: bits 18-19, fan2: bits 20-21). Mapped from BambuStudio's DryFanStatus enum.
AMSDryerRefusal
Bases: IntEnum
Why an AMS dryer cannot start now, from each AMS block's dry_sf_reason list.
Mapped from BambuStudio's DevAms::CannotDryReason. The printer sends an empty
list when a dry can start, and DRYING_IN_PROGRESS alone while one runs.
AMSDryerSubStatus
Bases: IntEnum
AMS drying sub-status extracted from bits 22-25 of ams_info. Indicates the specific phase of the drying cycle. Mapped from BambuStudio's DrySubStatus enum.
AMSHeatingState
Bases: IntEnum
AMS Drying/Heater states extracted from bits 4-7 of ams_info. Mapped directly from BambuStudio's DryStatus enum. Only supported on AMS 2 Pro (N3F) and AMS HT (N3S) models.
AMSModel
Bases: IntEnum
AMS model enum
AMSSeries
Bases: Enum
AMS Series enum
AMSUserSetting
Bases: Enum
AMS User Settings enum
ActiveTool
Bases: IntEnum
The currently active extruder index.
SINGLE_EXTRUDER (-1): Standard single-toolhead architecture (X1/P1/A1).RIGHT_EXTRUDER (0): The primary/right toolhead in H2D (Dual Extruder) systems.LEFT_EXTRUDER (1): The secondary/left toolhead in H2D (Dual Extruder) systems.NOT_ACTIVE (15): The multi-extruder system is in a transitional state.
AirConditioningMode
Bases: IntEnum
The mode the printer's air conditioning sub system is in.
NOT_SUPPORTED (-1): This printer is not equipped with this feature.COOL_MODE (0): The printer is currently not heating. The top vent may be open if the exhaust fan is running.HEAT_MODE (1): The printer is actively heating the chamber with the recirculation fan active.
DetectorSensitivity
Bases: Enum
Sensitivity level for X-Cam AI vision detectors (spaghetti, pile-up, clump, air-print).
The string value is sent directly in the halt_print_sensitivity MQTT field.
ExtensionToolMountState
Bases: IntEnum
Toolhead extension-tool mount state reported in device.ext_tool.mount_3d.
Values are mapped from BambuStudio's DevExtensionTool MountState enum.
UNKNOWN (-1): No telemetry received yet, or unrecognized value.NOT_MOUNTED (0): No extension tool mounted.MOUNTED (1): Extension tool mounted and communicating.NO_MODULE (2): Mount detected but no module present.NO_CABLE (3): Module mounted but its cable is not connected.
ExtensionToolType
Bases: Enum
Toolhead extension-tool type reported in device.ext_tool.type.
Values are the raw type codes mapped from BambuStudio's DevExtensionTool
(TOOL_TYPE_* enum). An unrecognized code resolves to UNKNOWN; the raw
code is preserved in ExtensionToolState.type_raw.
NONE (""): No extension tool attached (or the attached tool is unpowered).CUTTING_MODULE ("CP00"): Blade cutting module.LASER_MODULE ("LB00"): Laser module.ENHANCED_COOLING_FAN ("F000"): Toolhead Enhanced Cooling Fan.UNKNOWN ("?"): Unrecognized type code (future extension tool).
ExtruderInfoState
Bases: IntEnum
Consolidated logical states for extruder sensor status. Values represent unique state IDs to prevent bitmask collisions.
ExtruderStatus
Bases: IntEnum
Operational states for physical extruders. Values are mapped directly from the Bambu Studio source code (BBL_EXTRUDER_STATE).
FilamentStep
Bases: IntEnum
The step of a filament load or unload, named as Bambu Studio's DevFilamentStep.
FILAMENT_STEP_TEXT holds Studio's label for each step.
FilamentStepType
Bases: IntEnum
The kind of filament change under way, as Bambu Studio's FilamentStepType.
LOAD: a load from an AMS slot.UNLOAD: an unload (the target slot is none).VT_LOAD: a load from an external spool holder (254 or 255).
HMSAction
Bases: IntEnum
The dialog buttons Bambu Studio knows, by the ids its HMS action table uses
(DeviceErrorDialog.hpp ActionButton). Ids the table carries but this enum
lacks are buttons Studio does not show.
NozzleDiameter
Bases: Enum
Nozzle Diameter enum
NozzleFlowType
Bases: Enum
Canonical nozzle flow families used by BambuStudio/Orca nozzle identifiers.
Encoded as the second character in identifiers such as HS00-0.4.
NozzleMaterialCode
Bases: Enum
Canonical material codes used in encoded nozzle identifiers.
Encoded as characters 3-4 in identifiers such as HS00-0.4.
NozzleType
Bases: Enum
Canonical cross-model nozzle material type from telemetry.
This enum intentionally models only material/type categories that appear in
telemetry and accessory APIs. Encoded variant identifiers such as HS01
are parsed via parse_nozzle_identifier() / parse_nozzle_type().
PlateType
Bases: Enum
Used by BambuPrinter.print_3mf_file to specify which plate should be used when
starting a print job.
PrintOption
Bases: Enum
Print Option enum
PrinterModel
Bases: Enum
Printer model enum
PrinterSeries
Bases: Enum
Printer Series enum
ServiceState
Bases: Enum
This enum is used by bambu-printer-manager to track the underlying state
of the mqtt connection to the printer.
SpeedLevel
Bases: IntEnum
Print speed profile levels. Maps human-readable names to the firmware integer codes
sent in the print_speed MQTT command (param field) and reported back in spd_lvl.
Used by BambuPrinter.speed_level getter (returns the active level) and setter
(accepts a SpeedLevel to change the active profile).
TrayState
Bases: IntEnum
Operational status of the filament tray.
build_nozzle_identifier
build_nozzle_identifier(
flow_type: NozzleFlowType, nozzle_type: NozzleType, diameter: float | str
) -> str
Build a canonical Bambu-style nozzle identifier.
Examples
HS00-0.4, HH01-0.6, HU00-0.4
Source code in src/bpm/bambutools.py
def build_nozzle_identifier(
flow_type: NozzleFlowType, nozzle_type: NozzleType, diameter: float | str
) -> str:
"""
Build a canonical Bambu-style nozzle identifier.
Examples
--------
`HS00-0.4`, `HH01-0.6`, `HU00-0.4`
"""
material = _TYPE_TO_MATERIAL_CODE.get(nozzle_type)
if material is None:
raise ValueError(f"Unsupported nozzle_type for encoded ID: {nozzle_type}")
if flow_type not in {
NozzleFlowType.STANDARD,
NozzleFlowType.HIGH_FLOW,
NozzleFlowType.TPU_HIGH_FLOW,
}:
raise ValueError(f"Unsupported flow_type for encoded ID: {flow_type}")
if isinstance(diameter, float):
diameter_str = f"{diameter:.1f}"
else:
diameter_str = str(diameter)
return f"H{flow_type.value}{material.value}-{diameter_str}"
cache_delete
Remove cache_dir/<key>.json if it exists.
Silently ignores missing files and all other errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Path
|
Directory under |
required |
|
str
|
Filename stem produced by :func: |
required |
Source code in src/bpm/bambutools.py
def cache_delete(cache_dir: Path, key: str) -> None:
"""
Remove ``cache_dir/<key>.json`` if it exists.
Silently ignores missing files and all other errors.
Args:
cache_dir: Directory under ``bpm_cache_path``.
key: Filename stem produced by :func:`make_cache_key`.
"""
try:
(cache_dir / f"{key}.json").unlink(missing_ok=True)
except Exception:
pass
cache_read
Load a persisted value from cache_dir/<key>.json.
Returns default (None unless overridden) on any error — missing
file, corrupt JSON, permission error, etc. Callers should always
treat None / default as a cache miss and fall back gracefully.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Path
|
Directory under |
required |
|
str
|
Filename stem produced by :func: |
required |
|
Any
|
Value to return on any read failure (default |
None
|
Returns:
| Type | Description |
|---|---|
Any
|
The deserialized value, or default. |
Source code in src/bpm/bambutools.py
def cache_read(cache_dir: Path, key: str, default: Any = None) -> Any:
"""
Load a persisted value from ``cache_dir/<key>.json``.
Returns *default* (``None`` unless overridden) on any error — missing
file, corrupt JSON, permission error, etc. Callers should always
treat ``None`` / *default* as a cache miss and fall back gracefully.
Args:
cache_dir: Directory under ``bpm_cache_path``.
key: Filename stem produced by :func:`make_cache_key`.
default: Value to return on any read failure (default ``None``).
Returns:
The deserialized value, or *default*.
"""
try:
return json.loads((cache_dir / f"{key}.json").read_text())
except Exception:
return default
cache_write
Persist value to cache_dir/<key>.json.
value must be JSON-serializable (dict, list, scalar). The cache directory is created if it does not already exist. All errors are silently swallowed so callers never need to guard this call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
Path
|
Directory under |
required |
|
str
|
Filename stem produced by :func: |
required |
|
Any
|
Any JSON-serializable object to persist. |
required |
Source code in src/bpm/bambutools.py
def cache_write(cache_dir: Path, key: str, value: Any) -> None:
"""
Persist *value* to ``cache_dir/<key>.json``.
*value* must be JSON-serializable (dict, list, scalar). The cache
directory is created if it does not already exist. All errors are
silently swallowed so callers never need to guard this call.
Args:
cache_dir: Directory under ``bpm_cache_path`` (e.g. ``…/elapsed``).
key: Filename stem produced by :func:`make_cache_key`.
value: Any JSON-serializable object to persist.
"""
try:
cache_dir.mkdir(parents=True, exist_ok=True)
(cache_dir / f"{key}.json").write_text(json.dumps(value))
except Exception:
pass
decodeError
Decodes a raw print_error integer into a full HMS dictionary.
Source code in src/bpm/bambutools.py
def decodeError(error: int) -> dict:
"""
Decodes a raw print_error integer into a full HMS dictionary.
"""
if error == 0:
return {}
raw_hex = f"{error:08X}".upper()
wiki_slug = "-".join(raw_hex[i : i + 4] for i in range(0, 8, 4))
res = {
"code": f"HMS_{wiki_slug}",
"msg": "Unknown HMS Error",
"module": "System",
"severity": "Error",
"is_critical": False,
"type": "device_error",
# e.bambulab.com has pages only for 16-digit HMS codes; an 8-digit print_error
# lands on its home page, so point at Bambu's table of these codes instead
"url": "https://wiki.bambulab.com/en/hms/error-code",
}
real_module = (error >> 24) & 0xFF
module_map = {
0x03: "Mainboard",
0x05: "AMS",
0x12: "AMS",
0x07: "Toolhead",
0x0B: "Webcam",
0x10: "HMS",
}
res["module"] = module_map.get(real_module, "System")
msg_list = HMS_STATUS.get("data", {}).get("device_error", {}).get("en", [])
modules = ["03", "05", "07", "0B", "0C", "10", "12"]
if raw_hex[:2] in modules:
modules.remove(raw_hex[:2])
modules.insert(0, raw_hex[:2])
for module in modules:
current_hex = f"{module}{raw_hex[2:]}"
for entry in msg_list:
if entry.get("ecode", "").upper() == current_hex.upper():
res["msg"] = entry.get("intro", res["msg"])
break
if res["msg"] != "Unknown HMS Error":
break
mask = (error >> 16) & 0xFF
if mask in (0x00, 0x01):
res["severity"] = "Fatal" if mask == 0x00 else "Error"
res["is_critical"] = True
elif mask == 0x02:
res["severity"] = "Warning"
res["is_critical"] = False
else:
res["severity"] = "Info"
res["is_critical"] = False
return res
decodeHMS
Decodes the raw HMS list from telemetry into a structured list of dictionaries.
Source code in src/bpm/bambutools.py
def decodeHMS(hms_list: list) -> list[dict]:
"""
Decodes the raw HMS list from telemetry into a structured list of dictionaries.
"""
decoded_errors = []
for item in hms_list:
if isinstance(item, dict) and isinstance(item.get("code"), str):
decoded_errors.append(item)
continue
try:
attr = int(item.get("attr", 0))
code = int(item.get("code", 0))
except (AttributeError, ValueError, TypeError):
continue
if attr == 0:
continue
ecode = f"{attr:08X}{code:08X}"
wiki_slug = "-".join(ecode[i : i + 4] for i in range(0, 16, 4))
res = {
"code": f"HMS_{wiki_slug}",
"msg": "Unknown HMS Error",
"module": "System",
"severity": "Error",
"is_critical": False,
"type": "device_hms",
"url": f"https://e.bambulab.com/?e={ecode}",
}
mid = (attr >> 24) & 0xFF
mask = (attr >> 16) & 0xFF
module_map = {
0x03: "Mainboard",
0x05: "AMS",
0x12: "AMS",
0x07: "Toolhead",
0x0B: "Webcam",
0x10: "HMS",
}
res["module"] = module_map.get(mid, "System")
msg_list = HMS_STATUS.get("data", {}).get("device_hms", {}).get("en", [])
for entry in msg_list:
if entry.get("ecode", "").upper() == ecode:
res["msg"] = entry.get("intro", res["msg"])
break
if mask in (0x00, 0x01):
res["severity"], res["is_critical"] = (
("Fatal" if mask == 0x00 else "Error"),
True,
)
elif mask == 0x02:
res["severity"] = "Warning"
else:
res["severity"] = "Info"
decoded_errors.append(res)
return decoded_errors
dryerRefusalMessage
Explains why a dryer cannot start, the way BambuStudio does (AMSDryControl.cpp update_state and organize_cannot_reasons_text).
Returns "" when a dry can start: no reasons, or only DRYING_IN_PROGRESS.
Otherwise one line per reason shown: the first of task in progress, AMS busy
or filament at the outlet; then insufficient power; then the remaining
known reasons in the order received. When none of those apply (task
occupied or a value this table does not know), Studio's "System is busy".
Source code in src/bpm/bambutools.py
def dryerRefusalMessage(reasons: list[int]) -> str:
"""
Explains why a dryer cannot start, the way BambuStudio does
(AMSDryControl.cpp update_state and organize_cannot_reasons_text).
Returns "" when a dry can start: no reasons, or only `DRYING_IN_PROGRESS`.
Otherwise one line per reason shown: the first of task in progress, AMS busy
or filament at the outlet; then insufficient power; then the remaining
known reasons in the order received. When none of those apply (task
occupied or a value this table does not know), Studio's "System is busy".
"""
if not reasons or set(reasons) == {AMSDryerRefusal.DRYING_IN_PROGRESS}:
return ""
def line(reason: int) -> str:
title, detail = AMS_DRYER_REFUSAL_TEXT.get(reason, AMS_DRYER_REFUSAL_DEFAULT_TEXT)
return f"{title}. {detail}" if detail else f"{title}."
lines = []
for primary in (
AMSDryerRefusal.DRYING_IN_PROGRESS,
AMSDryerRefusal.AMS_BUSY,
AMSDryerRefusal.FILAMENT_AT_OUTLET,
AMSDryerRefusal.FILAMENT_AT_OUTLET_MANUAL_UNLOAD,
):
if primary in reasons:
lines.append(line(primary))
break
if AMSDryerRefusal.INSUFFICIENT_POWER in reasons:
lines.append(line(AMSDryerRefusal.INSUFFICIENT_POWER))
for reason in reasons:
if reason in (
AMSDryerRefusal.INSUFFICIENT_POWER_PLUG_IN,
AMSDryerRefusal.NOT_SUPPORTED_IN_2D_MODE,
AMSDryerRefusal.INITIATING_DRYING,
AMSDryerRefusal.UPGRADING,
):
lines.append(line(reason))
if not lines:
lines.append(line(-1))
return "\n".join(lines)
getAMSHeatingState
getAMSHeatingState(ams_info: int) -> AMSHeatingState
Decodes the AMS drying/heater state from bits 4-7 of the ams_info value.
This implementation follows BambuStudio's DevFilaSystem parsing logic, extracting DryStatus from the info field.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
int
|
Numeric AMS info value containing drying status in bits 4-7. |
required |
Returns:
| Type | Description |
|---|---|
AMSHeatingState
|
AMSHeatingState enum representing the current drying/heater state. |
AMSHeatingState
|
Only AMS 2 Pro (N3F) and AMS HT (N3S) support active drying states. |
Source code in src/bpm/bambutools.py
def getAMSHeatingState(ams_info: int) -> AMSHeatingState:
"""
Decodes the AMS drying/heater state from bits 4-7 of the ams_info value.
This implementation follows BambuStudio's DevFilaSystem parsing logic,
extracting DryStatus from the info field.
Args:
ams_info: Numeric AMS info value containing drying status in bits 4-7.
Returns:
AMSHeatingState enum representing the current drying/heater state.
Only AMS 2 Pro (N3F) and AMS HT (N3S) support active drying states.
"""
# Extract DryStatus from bits 4-7 (right-shift by 4, mask 0x0F)
dry_status = (ams_info >> 4) & 0x0F
return AMSHeatingState(dry_status)
getAMSModelBySerial
getAMSModelBySerial(serial: str) -> AMSModel
Returns the hardware model based on the serial number prefix.
Source code in src/bpm/bambutools.py
def getAMSModelBySerial(serial: str) -> AMSModel:
"""
Returns the hardware model based on the serial number prefix.
"""
prefix = serial[:3].upper()
if prefix == "19C":
return AMSModel.AMS_2_PRO
if prefix == "19F":
return AMSModel.AMS_HT
if prefix == "006":
return AMSModel.AMS_1
if prefix == "03C":
return AMSModel.AMS_LITE
return AMSModel.UNKNOWN
getAMSSeriesByModel
Returns the AMS series enum based on the provided model.
Source code in src/bpm/bambutools.py
getPrinterModelBySerial
getPrinterModelBySerial(serial: str) -> PrinterModel
Returns the Printer model enum based on the provided serial #.
Source code in src/bpm/bambutools.py
def getPrinterModelBySerial(serial: str) -> PrinterModel:
"""
Returns the Printer model enum based on the provided serial #.
"""
mapping = {
"00M": PrinterModel.X1C,
"00W": PrinterModel.X1,
"03W": PrinterModel.X1E,
"01S": PrinterModel.P1P,
"01P": PrinterModel.P1S,
"030": PrinterModel.A1_MINI,
"039": PrinterModel.A1,
"22E": PrinterModel.P2S,
"093": PrinterModel.H2S,
"094": PrinterModel.H2D,
}
prefix = serial[:3]
return mapping.get(prefix, PrinterModel.UNKNOWN)
getPrinterSeriesByModel
getPrinterSeriesByModel(model: PrinterModel) -> PrinterSeries
Returns the Printer series enum based on the provided model.
get_file_md5
get_file_md5(file_path: str | Path) -> str
Generates an MD5 hex hash for a given file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str | Path
|
The path to the file (string or Path object). |
required |
Returns:
| Type | Description |
|---|---|
str
|
The 32-character MD5 hexadecimal string. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If the file does not exist. |
Source code in src/bpm/bambutools.py
def get_file_md5(file_path: str | Path) -> str:
"""
Generates an MD5 hex hash for a given file.
Args:
file_path: The path to the file (string or Path object).
Returns:
The 32-character MD5 hexadecimal string.
Raises:
FileNotFoundError: If the file does not exist.
"""
path = Path(file_path)
if not path.is_file():
raise FileNotFoundError(f"No file found at: {path}")
md5_hash = hashlib.md5()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
md5_hash.update(chunk)
return md5_hash.hexdigest().upper()
hmsActions
The dialog buttons Bambu Studio shows for print_error on this printer, in order.
As DeviceErrorDialog::apply_result: an HMS_RETRY_CODES error gets Retry and
Confirm. Otherwise the error is looked up as %08X in HMS_ACTIONS under the
serial's first three characters, and an HMS_LIVEVIEW_CODES error adds View
Liveview. Ids Studio does not know, and REMOVE_CLOSE_BTN, are dropped.
Returns a list of {"id": int, "name": str, "label": str, "command": str}.
command is empty for a button bpm cannot send (see HMS_ACTION_COMMAND).
Source code in src/bpm/bambutools.py
def hmsActions(serial_number: str, print_error: int) -> list[dict]:
"""
The dialog buttons Bambu Studio shows for `print_error` on this printer, in order.
As `DeviceErrorDialog::apply_result`: an `HMS_RETRY_CODES` error gets Retry and
Confirm. Otherwise the error is looked up as `%08X` in `HMS_ACTIONS` under the
serial's first three characters, and an `HMS_LIVEVIEW_CODES` error adds View
Liveview. Ids Studio does not know, and `REMOVE_CLOSE_BTN`, are dropped.
Returns a list of `{"id": int, "name": str, "label": str, "command": str}`.
`command` is empty for a button bpm cannot send (see `HMS_ACTION_COMMAND`).
"""
if not print_error:
return []
ecode = f"{print_error & 0xFFFFFFFF:08X}"
code = f"{ecode[:4]}-{ecode[4:]}"
if code in HMS_RETRY_CODES:
ids = (HMSAction.DBL_CHECK_RETRY, HMSAction.DBL_CHECK_OK)
else:
table = HMS_ACTIONS.get(str(serial_number)[:3].upper())
ids = tuple(table["codes"].get(ecode, ())) if table else ()
if code in HMS_LIVEVIEW_CODES:
ids += (HMSAction.JUMP_TO_LIVEVIEW,)
actions = []
for action_id in ids:
if action_id not in HMSAction._value2member_map_:
continue
action = HMSAction(action_id)
if action == HMSAction.REMOVE_CLOSE_BTN:
continue
actions.append(
{
"id": action.value,
"name": action.name,
"label": HMS_ACTION_LABEL[action],
"command": HMS_ACTION_COMMAND.get(action, ""),
}
)
return actions
jsonSerializer
Module-level JSON serializer for use with json.dumps(default=jsonSerializer).
Handles dataclasses, objects with __dict__, and falls back to str().
Skips MQTT clients, threads, and mappingproxy instances that cannot be
serialized meaningfully.
Source code in src/bpm/bambutools.py
def jsonSerializer(obj: Any) -> Any:
"""
Module-level JSON serializer for use with `json.dumps(default=jsonSerializer)`.
Handles dataclasses, objects with `__dict__`, and falls back to `str()`.
Skips MQTT clients, threads, and `mappingproxy` instances that cannot be
serialized meaningfully.
"""
try:
if isinstance(obj, Thread):
return ""
if getattr(obj.__class__, "__module__", "").startswith("paho.mqtt"):
return ""
if str(obj.__class__).replace("<class '", "").replace("'>", "") == "mappingproxy":
return ""
return getattr(obj, "__dict__", str(obj))
except Exception:
return ""
make_cache_key
Sanitize a raw string into a filesystem-safe cache key.
Strips leading/trailing whitespace, replaces / and spaces with _,
and truncates to max_len characters. Returns None when the result
would be an empty string (i.e. the input carried no usable content).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
The source identifier (e.g. subtask name, gcode file path). |
required |
|
int
|
Maximum length of the returned key (default 80). |
80
|
Returns:
| Type | Description |
|---|---|
str | None
|
A non-empty sanitized string, or |
Source code in src/bpm/bambutools.py
def make_cache_key(raw: str, max_len: int = 80) -> str | None:
"""
Sanitize a raw string into a filesystem-safe cache key.
Strips leading/trailing whitespace, replaces ``/`` and spaces with ``_``,
and truncates to *max_len* characters. Returns ``None`` when the result
would be an empty string (i.e. the input carried no usable content).
Args:
raw: The source identifier (e.g. subtask name, gcode file path).
max_len: Maximum length of the returned key (default 80).
Returns:
A non-empty sanitized string, or ``None``.
"""
sanitized = raw.strip().replace("/", "_").replace(" ", "_")
return sanitized[:max_len] or None
nozzle_type_to_telemetry
nozzle_type_to_telemetry(value: NozzleType) -> str
parseAMSInfo
parseAMSInfo(info_hex: str) -> dict
Extracts all documented telemetry attributes from the AMS info hex string.
Based on BambuStudio's DevFilaSystemParser::ParseAmsInfo implementation. Complete bit field mapping (32-bit integer): - Bits 0-3: AMS type (1=AMS, 2=AMS_LITE, 3=AMS_2_PRO/N3F, 4=AMS_HT/N3S) - Bits 4-7: Dry status (OFF/CHECKING/DRYING/COOLING/STOPPING/ERROR/etc) - Bits 8-11: Extruder ID (for H2D toolhead assignment) - Bits 18-19: Dry fan 1 status (OFF=0, ON=1) - Bits 20-21: Dry fan 2 status (OFF=0, ON=1) - Bits 22-25: Dry sub-status (OFF/HEATING/DEHUMIDIFY)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
str
|
Hexadecimal string representation of AMS info value |
required |
Returns:
| Type | Description |
|---|---|
dict
|
Dictionary with all extracted telemetry fields |
Source code in src/bpm/bambutools.py
def parseAMSInfo(info_hex: str) -> dict:
"""
Extracts all documented telemetry attributes from the AMS info hex string.
Based on BambuStudio's DevFilaSystemParser::ParseAmsInfo implementation.
Complete bit field mapping (32-bit integer):
- Bits 0-3: AMS type (1=AMS, 2=AMS_LITE, 3=AMS_2_PRO/N3F, 4=AMS_HT/N3S)
- Bits 4-7: Dry status (OFF/CHECKING/DRYING/COOLING/STOPPING/ERROR/etc)
- Bits 8-11: Extruder ID (for H2D toolhead assignment)
- Bits 18-19: Dry fan 1 status (OFF=0, ON=1)
- Bits 20-21: Dry fan 2 status (OFF=0, ON=1)
- Bits 22-25: Dry sub-status (OFF/HEATING/DEHUMIDIFY)
Args:
info_hex: Hexadecimal string representation of AMS info value
Returns:
Dictionary with all extracted telemetry fields
"""
info = int(info_hex, 16)
ams_type = info & 0x0F # Bits 0-3
dry_status = (info >> 4) & 0x0F # Bits 4-7
extruder_id = (info >> 8) & 0x0F # Bits 8-11
dry_fan1_status = (info >> 18) & 0x03 # Bits 18-19
dry_fan2_status = (info >> 20) & 0x03 # Bits 20-21
dry_sub_status = (info >> 22) & 0x0F # Bits 22-25
ret = {
"ams_type": AMSModel(ams_type) if ams_type in range(5) else AMSModel.UNKNOWN,
"heater_state": AMSHeatingState(dry_status),
"extruder_id": extruder_id,
"dry_fan1_status": AMSDryerFanStatus(dry_fan1_status),
"dry_fan2_status": AMSDryerFanStatus(dry_fan2_status),
"dry_sub_status": AMSDryerSubStatus(dry_sub_status),
}
# print(f"\r\n{ret}\r\n")
return ret
parseAMSStatus
Maps the ams_status code to human-readable descriptions.
Source code in src/bpm/bambutools.py
def parseAMSStatus(status_int: int) -> str:
"""
Maps the ams_status code to human-readable descriptions.
"""
main_status = (status_int >> 8) & 0xFF
status_map = {
0x00: "Idle",
0x01: "Filament Changing",
0x02: "RFID Identifying",
0x03: "Assist/Engaged",
0x04: "Calibration",
0x10: "Self Check",
0x20: "Debug",
0xFF: "Unknown",
}
return status_map.get(main_status, "Idle")
parseExtruderInfo
parseExtruderInfo(info_int: int) -> ExtruderInfoState
Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names.
Source code in src/bpm/bambutools.py
def parseExtruderInfo(info_int: int) -> ExtruderInfoState:
"""
Decodes the extruder 'info' bit-packed status using unique ExtruderInfoState names.
"""
if not (info_int & 0x08):
return ExtruderInfoState.NO_NOZZLE
if info_int & 0x02:
return ExtruderInfoState.LOADED
if info_int & 0x04:
return ExtruderInfoState.BUFFER_LOADED
return ExtruderInfoState.EMPTY
parseExtruderStatus
parseExtruderStatus(stat_int: int) -> ExtruderStatus
Decodes the operational extruder state using the exhaustive Enum map.
Source code in src/bpm/bambutools.py
def parseExtruderStatus(stat_int: int) -> ExtruderStatus:
"""
Decodes the operational extruder state using the exhaustive Enum map.
"""
working_bits = (stat_int >> 8) & 0x03
if working_bits in (0x02, 0x03):
return ExtruderStatus.ACTIVE
if stat_int & 0x01:
return ExtruderStatus.HEATING
return ExtruderStatus.IDLE
parseExtruderTrayState
Decodes an extruder's tray from its device.extruder.info[] hnow/snow (or
htar/star) pair.
Parameters
- extruder : int - the extruder id (0 right, 1 left).
- hotend : int -
hnoworhtar; 254 means the external holder. - slot : int -
snoworstar.
Returns the extruder's external holder (255 right, 254 left) when hotend is 254 or
slot is 0xFF00 (right) / 0xFE00 (left); -1 when the low byte is 255 (no tray, e.g.
0xFFFF right or 0xFEFF left, an unload's target); otherwise the slot's low byte.
Source code in src/bpm/bambutools.py
def parseExtruderTrayState(extruder: int, hotend, slot) -> int:
"""
Decodes an extruder's tray from its `device.extruder.info[]` `hnow`/`snow` (or
`htar`/`star`) pair.
Parameters
----------
* extruder : int - the extruder id (0 right, 1 left).
* hotend : int - `hnow` or `htar`; 254 means the external holder.
* slot : int - `snow` or `star`.
Returns the extruder's external holder (255 right, 254 left) when `hotend` is 254 or
`slot` is 0xFF00 (right) / 0xFE00 (left); -1 when the low byte is 255 (no tray, e.g.
0xFFFF right or 0xFEFF left, an unload's target); otherwise the slot's low byte.
"""
if (
hotend == 254
or (extruder == 0 and slot == 65280)
or (extruder == 1 and slot == 65024)
):
return 255 - extruder
# the low byte 255 is no slot (0xFFFF right, 0xFEFF left): no tray, an unload's target
if slot & 0xFF == 255:
return -1
else:
return slot & 0xFF
parseFilamentStep
parseFilamentStep(
ams_status: int,
target_tray_id: int,
filament_at_extruder: bool = True,
previous: FilamentStep = IDLE,
) -> tuple[FilamentStep, FilamentStepType]
Names the filament load or unload step, as Bambu Studio's StatusPanel does.
Parameters
- ams_status : int - raw
ams_status; the high byte 1 is a filament change and the low byte is the step code. - target_tray_id : int - the loading extruder's target tray: 254 or 255 is an external
holder (
VT_LOAD), -1 is no target (UNLOAD), anything else is an AMS load. - filament_at_extruder : bool - the printer's
hw_switch_state(True when unknown). Studio names codes 0x05 and 0x07 of an AMS load differently when it is False. - previous : FilamentStep - the step before; code 0x09 is a wait that keeps it.
Returns (FilamentStep, FilamentStepType); IDLE when no change runs or the code
is not one Studio names for that kind of change.
Source code in src/bpm/bambutools.py
def parseFilamentStep(
ams_status: int,
target_tray_id: int,
filament_at_extruder: bool = True,
previous: FilamentStep = FilamentStep.IDLE,
) -> tuple[FilamentStep, FilamentStepType]:
"""
Names the filament load or unload step, as Bambu Studio's `StatusPanel` does.
Parameters
----------
* ams_status : int - raw `ams_status`; the high byte 1 is a filament change and the
low byte is the step code.
* target_tray_id : int - the loading extruder's target tray: 254 or 255 is an external
holder (`VT_LOAD`), -1 is no target (`UNLOAD`), anything else is an AMS load.
* filament_at_extruder : bool - the printer's `hw_switch_state` (True when unknown).
Studio names codes 0x05 and 0x07 of an AMS load differently when it is False.
* previous : FilamentStep - the step before; code 0x09 is a wait that keeps it.
Returns `(FilamentStep, FilamentStepType)`; `IDLE` when no change runs or the code
is not one Studio names for that kind of change.
"""
if (ams_status >> 8) & 0xFF != 0x01:
return FilamentStep.IDLE, FilamentStepType.LOAD
sub = ams_status & 0xFF
if target_tray_id in (254, 255):
vt_steps = {
0x02: FilamentStep.HEAT_NOZZLE,
0x05: FilamentStep.PUSH_NEW_FILAMENT,
0x06: FilamentStep.CONFIRM_EXTRUDED,
0x07: FilamentStep.PURGE_OLD_FILAMENT,
}
return vt_steps.get(sub, FilamentStep.IDLE), FilamentStepType.VT_LOAD
unload = target_tray_id == -1
step_type = FilamentStepType.UNLOAD if unload else FilamentStepType.LOAD
with_temp = not unload and not filament_at_extruder
steps = {
0x02: FilamentStep.HEAT_NOZZLE,
0x03: FilamentStep.CUT_FILAMENT,
0x04: FilamentStep.PULL_CURR_FILAMENT,
0x05: FilamentStep.CUT_FILAMENT if with_temp else FilamentStep.PUSH_NEW_FILAMENT,
0x06: FilamentStep.PUSH_NEW_FILAMENT,
0x07: FilamentStep.PULL_CURR_FILAMENT
if with_temp
else FilamentStep.PURGE_OLD_FILAMENT,
0x08: FilamentStep.CHECK_POSITION,
0x0B: FilamentStep.CHECK_POSITION,
}
if sub == 0x09:
return previous, step_type
if sub not in steps:
return FilamentStep.IDLE, FilamentStepType.UNLOAD
return steps[sub], step_type
parseRFIDStatus
Can be used to parse ams_rfid_status
Source code in src/bpm/bambutools.py
parseStage
Maps stg_cur numeric codes to human-readable print stages.
Source code in src/bpm/bambutools.py
def parseStage(stage_int: int) -> str:
"""
Maps stg_cur numeric codes to human-readable print stages.
"""
stage_map = {
-1: "",
0: "",
1: "Auto bed leveling",
2: "Heatbed preheating",
3: "Sweeping XY mech mode",
4: "Changing filament",
5: "M400 pause",
6: "Filament runout pause",
7: "Heating hotend",
8: "Calibrating extrusion",
9: "Scanning bed surface",
10: "Inspecting first layer",
11: "Identifying build plate",
12: "Calibrating Micro Lidar",
13: "Homing toolhead",
14: "Cleaning nozzle tip",
15: "Temp check",
16: "Paused by user",
17: "Front cover falling",
18: "Lidar calibration (alt)",
19: "Calibrating flow",
20: "Nozzle temp malfunction",
21: "Bed temp malfunction",
22: "Filament unloading",
23: "Skip step pause",
24: "Filament loading",
25: "Motor noise calibration",
26: "AMS lost pause",
27: "Fan speed pause",
28: "Chamber control error",
29: "Cooling chamber",
30: "Custom Gcode pause",
31: "Motor noise showoff",
32: "Nozzle cover pause",
33: "Cutter error pause",
34: "First layer error pause",
35: "Nozzle clog pause",
36: "Absolute accuracy pre-check",
37: "Chamber control",
38: "Absolute accuracy post-check",
39: "Nozzle Offset Calibration",
40: "Bed level high temperature",
41: "Check quick release",
42: "Check door and cover",
43: "Laser calibration",
44: "Check platform",
45: "Check birdeye camera position",
46: "Calibrate birdeye camera",
47: "Bed level phase 1",
48: "Bed level phase 2",
49: "Heating chamber",
50: "Heated bed cooling",
51: "Print calibration lines",
52: "Check material",
53: "Calibrating live view camera",
54: "Waiting for heatbed temperature",
55: "Check material position",
56: "Calibrating cutter model offset",
57: "Measuring surface",
58: "Thermal preconditioning",
70: "Leading filament",
71: "Reached toolhead",
72: "Hotend type detection",
73: "Build-plate alignment",
74: "Heatbed object detection",
75: "Heatbed object detection",
76: "Cutting",
77: "Tool switching",
100: "Printing",
245: "Enhanced fan full speed",
246: "Enhanced fan half speed",
247: "Enhanced fan off",
255: "Completed",
}
return stage_map.get(stage_int, f"Stage [{stage_int}]")
parse_nozzle_identifier
parse_nozzle_identifier(nozzle_id: str) -> tuple[NozzleFlowType, NozzleType, str]
Parse a Bambu-style nozzle identifier into flow family, material, and diameter.
Supported encoded formats include values such as HS00-0.4, HH01-0.6,
and HU00-0.4.
Returns
tuple[NozzleFlowType, NozzleType, str]
A tuple of (flow_type, nozzle_type, diameter).
Unknown/unsupported parts are returned as UNKNOWN enum values.
Source code in src/bpm/bambutools.py
def parse_nozzle_identifier(nozzle_id: str) -> tuple[NozzleFlowType, NozzleType, str]:
"""
Parse a Bambu-style nozzle identifier into flow family, material, and diameter.
Supported encoded formats include values such as `HS00-0.4`, `HH01-0.6`,
and `HU00-0.4`.
Returns
-------
tuple[NozzleFlowType, NozzleType, str]
A tuple of `(flow_type, nozzle_type, diameter)`.
Unknown/unsupported parts are returned as `UNKNOWN` enum values.
"""
if not nozzle_id:
return (NozzleFlowType.UNKNOWN, NozzleType.UNKNOWN, "")
clean = nozzle_id.strip()
if "-" in clean:
encoded, diameter = clean.split("-", 1)
else:
encoded, diameter = clean, ""
if len(encoded) < 4:
return (NozzleFlowType.UNKNOWN, NozzleType.UNKNOWN, diameter)
flow = _FLOW_CODE_TO_TYPE.get(encoded[1], NozzleFlowType.UNKNOWN)
material_code = encoded[2:4]
nozzle_type = _MATERIAL_CODE_TO_TYPE.get(material_code, NozzleType.UNKNOWN)
return (flow, nozzle_type, diameter)
parse_nozzle_type
parse_nozzle_type(value: str | None) -> NozzleType
Resolve a nozzle type from cross-model telemetry strings or encoded IDs.
Supports direct telemetry values (for example hardened_steel) and encoded
forms (for example HH01-0.4, HS00, HU05).
Source code in src/bpm/bambutools.py
def parse_nozzle_type(value: str | None) -> NozzleType:
"""
Resolve a nozzle type from cross-model telemetry strings or encoded IDs.
Supports direct telemetry values (for example `hardened_steel`) and encoded
forms (for example `HH01-0.4`, `HS00`, `HU05`).
"""
if value is None:
return NozzleType.UNKNOWN
clean = value.strip()
if not clean:
return NozzleType.UNKNOWN
telemetry_key = clean.lower()
if telemetry_key in _NOZZLE_TYPE_TELEMETRY_TO_ENUM:
return _NOZZLE_TYPE_TELEMETRY_TO_ENUM[telemetry_key]
if clean in _MATERIAL_CODE_TO_TYPE:
return _MATERIAL_CODE_TO_TYPE[clean]
_, nozzle_type, _ = parse_nozzle_identifier(clean)
if nozzle_type != NozzleType.UNKNOWN:
return nozzle_type
try:
return NozzleType[clean.upper()]
except (KeyError, ValueError):
return NozzleType.UNKNOWN
printer_cache_dir
One printer's own cache folder: bpm_cache_path/<serial>, or bpm_cache_path
itself when no serial is set.
Every cached file is filed per printer so containers sharing one cache folder never read each other's job record, start time or project metadata.
Source code in src/bpm/bambutools.py
def printer_cache_dir(bpm_cache_path: Path | None, serial_number: str | None) -> Path:
"""
One printer's own cache folder: ``bpm_cache_path/<serial>``, or ``bpm_cache_path``
itself when no serial is set.
Every cached file is filed per printer so containers sharing one cache folder never
read each other's job record, start time or project metadata.
"""
base = bpm_cache_path or Path()
return base / serial_number if serial_number else base
resolve_external_spool_trays
resolve_external_spool_trays(
filament_extruders: list[int],
physical_extruder_map: list[int],
used_filament_ids: list[int],
) -> list[int]
Name the external spool holder that feeds each filament of a sliced plate on a dual-nozzle printer.
The slicer fixes which extruder prints each filament, so the holder is derived
from the plate, never chosen by the caller. filament_extruders (the plate's
filament_maps in slice_info.config) gives each filament's 1-based logical
extruder, and physical_extruder_map (from the plate gcode config block) maps
that logical extruder to a physical one, 0 for the main extruder and 1 for
the deputy. On H2D the main extruder is the right one and the map is 1,0, so
logical extruder 1 is the LEFT extruder.
Parameters
- filament_extruders : list[int] - 1-based logical extruder per filament,
index
iis filament idi + 1. - physical_extruder_map : list[int] - physical extruder per logical extruder,
index
jis logical extruderj + 1. - used_filament_ids : list[int] - 1-based ids of the filaments the plate uses.
Returns
A list the length of filament_extruders: VIRTUAL_TRAY_MAIN_ID (255) or
VIRTUAL_TRAY_DEPUTY_ID (254) at each used filament's index, -1 elsewhere.
Raises
ValueError when the plate carries no extruder assignment or no physical map,
or names an extruder or filament id outside them. A wrong holder prints on the
wrong side, so this never guesses.
Source code in src/bpm/bambutools.py
def resolve_external_spool_trays(
filament_extruders: list[int],
physical_extruder_map: list[int],
used_filament_ids: list[int],
) -> list[int]:
"""
Name the external spool holder that feeds each filament of a sliced plate on a
dual-nozzle printer.
The slicer fixes which extruder prints each filament, so the holder is derived
from the plate, never chosen by the caller. `filament_extruders` (the plate's
`filament_maps` in `slice_info.config`) gives each filament's 1-based *logical*
extruder, and `physical_extruder_map` (from the plate gcode config block) maps
that logical extruder to a physical one, `0` for the main extruder and `1` for
the deputy. On H2D the main extruder is the right one and the map is `1,0`, so
logical extruder 1 is the LEFT extruder.
Parameters
----------
* filament_extruders : list[int] - 1-based logical extruder per filament,
index `i` is filament id `i + 1`.
* physical_extruder_map : list[int] - physical extruder per logical extruder,
index `j` is logical extruder `j + 1`.
* used_filament_ids : list[int] - 1-based ids of the filaments the plate uses.
Returns
-------
A list the length of `filament_extruders`: `VIRTUAL_TRAY_MAIN_ID` (255) or
`VIRTUAL_TRAY_DEPUTY_ID` (254) at each used filament's index, `-1` elsewhere.
Raises
------
`ValueError` when the plate carries no extruder assignment or no physical map,
or names an extruder or filament id outside them. A wrong holder prints on the
wrong side, so this never guesses.
"""
if not filament_extruders:
raise ValueError("plate has no filament extruder assignment (filament_maps)")
if not physical_extruder_map:
raise ValueError("plate has no physical_extruder_map")
trays = [-1] * len(filament_extruders)
for filament_id in used_filament_ids:
index = filament_id - 1
if not 0 <= index < len(filament_extruders):
raise ValueError(
f"filament id [{filament_id}] is outside the plate's filaments"
)
logical = filament_extruders[index]
if not 1 <= logical <= len(physical_extruder_map):
raise ValueError(
f"filament id [{filament_id}] uses logical extruder [{logical}] outside physical_extruder_map"
)
physical = physical_extruder_map[logical - 1]
if physical == 0:
trays[index] = VIRTUAL_TRAY_MAIN_ID
elif physical == 1:
trays[index] = VIRTUAL_TRAY_DEPUTY_ID
else:
raise ValueError(f"physical extruder [{physical}] is neither 0 nor 1")
return trays
scaleFanSpeed
Scales proprietary 0-15 fan speed values to a 0-100 percentage.
sortFileTreeAlphabetically
Sorts a dict of file/directory nodes hierarchically.
Source code in src/bpm/bambutools.py
def sortFileTreeAlphabetically(source) -> dict:
"""
Sorts a dict of file/directory nodes hierarchically.
"""
def sort_node_list(node_list):
for item in node_list:
if item.get("id", "").endswith("/") and "children" in item:
item["children"] = sort_node_list(item["children"])
return sorted(
node_list,
key=lambda i: (not i.get("id", "").endswith("/"), i.get("name", "").lower()),
)
source["children"] = sort_node_list(source["children"])
return source
unpackTemperature
Unpacks a 32-bit packed temperature integer into a tuple of (Actual, Target).