filament_ids.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """Utility functions for converting between filament_id and setting_id formats, and shared filament constants.
  2. Bambu printers use two ID formats for filament presets:
  3. - **filament_id** (aka tray_info_idx): e.g. "GFL05", "GFG02", "GFA00"
  4. Reported by printer firmware (RFID tags, AMS status).
  5. - **setting_id**: e.g. "GFSL05", "GFSG02", "GFSA00"
  6. Used by BambuStudio / Bambu Cloud API to resolve presets.
  7. For most official Bambu filaments the only difference is an "S" inserted after
  8. "GF". Two kinds do not follow that rule (#3387):
  9. - SUNLU: setting_id "GFSNLS03_04", filament_id "GFSNL03" -- a filament_id
  10. that itself starts with "GFS" plus a letter.
  11. - About thirty P1/X1-series presets whose filament_id is another one
  12. altogether ("GFSA05_10", Bambu PLA Matte @BBL P1P, is "GFA01", while every
  13. other "GFSA05" is Bambu PLA Silk, "GFA05"). Those are listed by full
  14. setting_id in ``filament_id_tables.SETTING_ID_FILAMENT_IDS``.
  15. User presets (starting with "P") use the same ID in both contexts.
  16. """
  17. import re
  18. from backend.app.utils.filament_id_tables import ORCA_FILAMENT_IDS, SETTING_ID_FILAMENT_IDS
  19. # SUNLU's ids in Bambu's catalogue: filament_id "GFSNL03", setting_id "GFSNLS03".
  20. _SUNLU_FILAMENT_ID = re.compile(r"GFSNL(\d+)")
  21. _SUNLU_SETTING_ID = re.compile(r"GFSNLS(\d+)")
  22. # OrcaSlicer's own filament ids ("OFMUWNkp"); Orca maps them to Bambu's at the
  23. # printer boundary with the table ORCA_FILAMENT_IDS is generated from.
  24. _ORCA_FILAMENT_ID = re.compile(r"OF[0-9A-Za-z]{6}")
  25. MATERIAL_TEMPS: dict[str, tuple[int, int]] = {
  26. "PLA": (190, 230),
  27. "PETG": (220, 260),
  28. "ABS": (240, 270),
  29. "ASA": (240, 270),
  30. "TPU": (200, 240),
  31. "PA": (260, 290),
  32. "PC": (250, 280),
  33. "PVA": (190, 210),
  34. "PLA-CF": (210, 240),
  35. "PETG-CF": (240, 270),
  36. "PA-CF": (270, 300),
  37. }
  38. # Bambu's generic preset per material, for a slot whose spool has no preset of
  39. # its own. Every assignment path and the Configure dialog read this one table;
  40. # a material missing here goes to the printer with an empty tray_info_idx (#3273).
  41. GENERIC_FILAMENT_IDS: dict[str, str] = {
  42. "PLA": "GFL99",
  43. "PETG": "GFG99",
  44. "ABS": "GFB99",
  45. "ASA": "GFB98",
  46. "PC": "GFC99",
  47. "PA": "GFN99",
  48. "NYLON": "GFN99",
  49. "TPU": "GFU99",
  50. "PVA": "GFS99",
  51. "HIPS": "GFS98",
  52. "PLA-CF": "GFL98",
  53. "PETG-CF": "GFG98",
  54. "PA-CF": "GFN98",
  55. "PETG HF": "GFG96",
  56. "PLA SILK": "GFL96",
  57. "PLA HIGH SPEED": "GFL95",
  58. "PCTG": "GFG97",
  59. "PE": "GFP99",
  60. "PP": "GFP97",
  61. }
  62. # The generics the slot-reuse check replaces instead of carrying forward:
  63. # a slot holding one of these gets the generic for the new spool's own
  64. # material. Deliberately the set from before the table above grew (#3273), so
  65. # a slot set to Generic PLA Silk or PLA High Speed still keeps it for a
  66. # preset-less PLA spool, as it always has.
  67. GENERIC_IDS_REPLACED_ON_REUSE: frozenset[str] = frozenset(
  68. {
  69. "GFL99",
  70. "GFL98",
  71. "GFG99",
  72. "GFG98",
  73. "GFG96",
  74. "GFB99",
  75. "GFB98",
  76. "GFC99",
  77. "GFN99",
  78. "GFN98",
  79. "GFU99",
  80. "GFS99",
  81. "GFS98",
  82. }
  83. )
  84. def filament_id_to_setting_id(filament_id: str) -> str:
  85. """Convert filament_id → setting_id (e.g. "GFL05" → "GFSL05").
  86. - Already a setting_id ("GFSL05", "GFSS99", "GFSNLS03") → returned
  87. unchanged; a support filament_id ("GFS99") still converts, and so does
  88. SUNLU's ("GFSNL03" → "GFSNLS03").
  89. - User presets ("P…") → returned unchanged.
  90. - Empty / unknown → returned unchanged.
  91. """
  92. if not filament_id:
  93. return filament_id
  94. # User presets start with "P" - leave unchanged
  95. if filament_id.startswith("P"):
  96. return filament_id
  97. sunlu = _SUNLU_FILAMENT_ID.fullmatch(filament_id)
  98. if sunlu:
  99. return f"GFSNLS{sunlu.group(1)}"
  100. # Official Bambu presets: GFx## -> GFSx##
  101. if filament_id.startswith("GF") and len(filament_id) >= 4:
  102. # Already a setting_id: "GFS" then the family letter ("GFSL05").
  103. # Support filaments are family S, so their filament_id starts with
  104. # "GFS" too -- but a digit follows it ("GFS99" is Generic PVA, whose
  105. # setting_id is "GFSS99").
  106. if filament_id[2] == "S" and filament_id[3].isalpha():
  107. return filament_id
  108. return f"GFS{filament_id[2:]}"
  109. return filament_id
  110. def setting_id_to_filament_id(setting_id: str) -> str:
  111. """Convert setting_id → filament_id (e.g. "GFSL05_07" → "GFL05"), with
  112. any version suffix stripped.
  113. - A setting_id Bambu gives a filament_id the rule does not lead to → that
  114. filament_id, looked up by the full id, suffix included (#3387).
  115. - Already a filament_id ("GFL05", a support one like "GFS99", or SUNLU's
  116. "GFSNL03") → returned unchanged.
  117. - User presets ("P…") → returned without the suffix.
  118. - Empty / unknown → returned without the suffix.
  119. """
  120. if not setting_id:
  121. return setting_id
  122. listed = SETTING_ID_FILAMENT_IDS.get(setting_id)
  123. if listed:
  124. return listed
  125. return derived_filament_id(setting_id.split("_")[0])
  126. def derived_filament_id(base: str) -> str:
  127. """The filament_id the naming rule gives for a suffix-free setting_id,
  128. without the table of exceptions. ``scripts/generate_filament_id_tables.py``
  129. lists exactly the presets where this differs from Bambu's own id."""
  130. sunlu = _SUNLU_SETTING_ID.fullmatch(base)
  131. if sunlu:
  132. return f"GFSNL{sunlu.group(1)}"
  133. if _SUNLU_FILAMENT_ID.fullmatch(base):
  134. return base
  135. # Setting_id format: GFSx## -> GFx## (remove the "S"). A digit after
  136. # "GFS" means a support filament_id, not a setting_id (see above).
  137. if base.startswith("GFS") and len(base) >= 5 and base[3].isalpha():
  138. return f"GF{base[3:]}"
  139. return base
  140. def orca_to_bambu_filament_id(filament_id: str) -> str:
  141. """The Bambu filament_id for an OrcaSlicer one ("OFMUWNkp" → "GFSNL03").
  142. OrcaSlicer's system presets carry ids of their own; the printer only knows
  143. Bambu's. An Orca id the table does not know returns ``""`` -- sent to the
  144. printer it would resolve nowhere. Any other id is returned unchanged.
  145. """
  146. if _ORCA_FILAMENT_ID.fullmatch(filament_id or ""):
  147. return ORCA_FILAMENT_IDS.get(filament_id, "")
  148. return filament_id
  149. def normalize_slicer_filament(slicer_filament: str | None) -> tuple[str, str]:
  150. """Normalize a slicer_filament value into (tray_info_idx, setting_id).
  151. The slicer_filament field on a spool can be stored in either format:
  152. - filament_id: "GFL05" (from RFID tag scan)
  153. - setting_id: "GFSL05" or "GFSL05_07" (from cloud preset picker)
  154. Returns (tray_info_idx, setting_id) with version suffixes stripped.
  155. """
  156. raw = slicer_filament or ""
  157. if not raw:
  158. return ("", "")
  159. # Strip version suffix (e.g. "GFSL05_07" → "GFSL05"); the filament_id is
  160. # read from the full id, since the suffix can decide it (#3387).
  161. base = raw.split("_")[0] if "_" in raw else raw
  162. tray_info_idx = setting_id_to_filament_id(raw)
  163. sid = filament_id_to_setting_id(base)
  164. return (tray_info_idx, sid)