generate_filament_id_tables.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """Regenerate the filament-id tables from Bambu's and OrcaSlicer's own data (#3387).
  2. Usage:
  3. python scripts/generate_filament_id_tables.py [BAMBU_PUBLIC_JSON] [ORCA_IDS_JSON]
  4. With no arguments both sources are downloaded:
  5. Bambu Cloud's public preset list (no login needed). Every filament entry
  6. carries its setting_id and the filament_id Bambu Studio sends to the printer
  7. for it. For most, dropping the "S" after "GF" turns one into the other; the
  8. presets where it does not (beyond SUNLU, which ``derived_filament_id``
  9. handles by rule) go into SETTING_ID_FILAMENT_IDS, by full setting_id --
  10. the suffix can decide it ("GFSA05_10" is Bambu PLA Matte, every other
  11. "GFSA05_nn" Bambu PLA Silk).
  12. A bare setting_id is listed only when the rule gives an id no preset has.
  13. Bambuddy itself builds bare ids from filament ids ("GFA05" -> "GFSA05"), so
  14. where the rule leads to a real filament ("GFSA05" -> Bambu PLA Silk), it
  15. wins over Bambu's legacy preset of that bare id ("GFSA05" is also Bambu PLA
  16. Matte @BBL X1): listing it would turn every such Silk spool into Matte.
  17. OrcaSlicer's resources/printers/bambu_filament_ids.json, the table Orca uses
  18. to translate its own filament ids ("OFMUWNkp") to Bambu's at the printer.
  19. It becomes ORCA_FILAMENT_IDS.
  20. Writes backend/app/utils/filament_id_tables.py and
  21. frontend/src/utils/filamentIdTables.ts (the frontend needs only the first
  22. table). Run it by hand when Bambu or Orca add filaments, and review the diff.
  23. """
  24. import json
  25. import sys
  26. import urllib.request
  27. from pathlib import Path
  28. REPO_ROOT = Path(__file__).resolve().parent.parent
  29. sys.path.insert(0, str(REPO_ROOT))
  30. from backend.app.utils.filament_ids import derived_filament_id # noqa: E402
  31. BAMBU_URL = "https://api.bambulab.com/v1/iot-service/api/slicer/setting?version=2.0.0.0&public=true"
  32. ORCA_URL = "https://raw.githubusercontent.com/OrcaSlicer/OrcaSlicer/main/resources/printers/bambu_filament_ids.json"
  33. BACKEND_OUT = REPO_ROOT / "backend" / "app" / "utils" / "filament_id_tables.py"
  34. FRONTEND_OUT = REPO_ROOT / "frontend" / "src" / "utils" / "filamentIdTables.ts"
  35. def _load(arg: str | None, url: str) -> dict:
  36. if arg:
  37. return json.loads(Path(arg).read_text())
  38. request = urllib.request.Request(url, headers={"User-Agent": "bambuddy-filament-id-tables"})
  39. with urllib.request.urlopen(request, timeout=30) as response:
  40. return json.load(response)
  41. def main() -> None:
  42. args = sys.argv[1:]
  43. bambu = _load(args[0] if args else None, BAMBU_URL)
  44. orca = _load(args[1] if len(args) > 1 else None, ORCA_URL)
  45. presets = (bambu.get("filament") or {}).get("public") or []
  46. if not presets:
  47. sys.exit("Bambu's public list has no filament presets")
  48. real_ids = {preset.get("filament_id") for preset in presets}
  49. setting_ids: dict[str, str] = {}
  50. for preset in presets:
  51. setting_id, filament_id = preset.get("setting_id"), preset.get("filament_id")
  52. if not setting_id or not filament_id:
  53. continue
  54. derived = derived_filament_id(setting_id.split("_")[0])
  55. if derived == filament_id or ("_" not in setting_id and derived in real_ids):
  56. continue
  57. setting_ids[setting_id] = filament_id
  58. orca_ids = {
  59. orca_id: entry["bambu_id"]
  60. for orca_id, entry in (orca.get("filaments") or {}).items()
  61. if isinstance(entry, dict) and entry.get("bambu_id")
  62. }
  63. if not orca_ids:
  64. sys.exit("Orca's table has no filaments")
  65. header = "Generated by scripts/generate_filament_id_tables.py -- do not edit by hand."
  66. lines = [f'"""{header}', "", f"{len(presets)} public Bambu filament presets read.", '"""', ""]
  67. lines.append("# Bambu setting_id -> filament_id, where the naming rule gives another id.")
  68. lines.append("SETTING_ID_FILAMENT_IDS: dict[str, str] = {")
  69. lines += [f' "{k}": "{v}",' for k, v in sorted(setting_ids.items())]
  70. lines.append("}")
  71. lines.append("")
  72. commit = orca.get("bambustudio_commit", "unknown")
  73. lines.append(f"# OrcaSlicer filament_id -> Bambu filament_id (Orca's table, Bambu Studio {commit}).")
  74. lines.append("ORCA_FILAMENT_IDS: dict[str, str] = {")
  75. lines += [f' "{k}": "{v}",' for k, v in sorted(orca_ids.items())]
  76. lines.append("}")
  77. BACKEND_OUT.write_text("\n".join(lines) + "\n")
  78. ts = [f"// {header}", "// Bambu setting_id -> filament_id, where the naming rule gives another id.", ""]
  79. ts.append("export const SETTING_ID_FILAMENT_IDS: Readonly<Record<string, string>> = {")
  80. ts += [f" {k}: '{v}'," for k, v in sorted(setting_ids.items())]
  81. ts.append("};")
  82. FRONTEND_OUT.write_text("\n".join(ts) + "\n")
  83. print(f"{len(setting_ids)} setting_id exceptions, {len(orca_ids)} Orca ids")
  84. if __name__ == "__main__":
  85. main()