fill_spool_effects.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/env python3
  2. """Seed Bambuddy with a quick visual set of filament effect spools.
  3. Usage:
  4. python scripts/fill_spool_effects.py --bambuddy-url http://localhost:8000
  5. python scripts/fill_spool_effects.py --bambuddy-url http://localhost:8000 --api-key YOUR_KEY
  6. This script creates stock spools for every effect type defined in the `SPOOLS` list below,
  7. using the bulk endpoint for creation:
  8. POST /api/v1/inventory/spools/bulk
  9. """
  10. from __future__ import annotations
  11. import argparse
  12. import sys
  13. from dataclasses import dataclass
  14. import requests
  15. API_PATH_BULK_CREATE = "/api/v1/inventory/spools/bulk"
  16. @dataclass(frozen=True)
  17. class TestSpool:
  18. "Class representing a spool definition and count for the test spool set"
  19. effect_type: str
  20. colors: dict[str, str]
  21. quantity: int = 1
  22. SPOOLS: list[TestSpool] = [
  23. TestSpool(
  24. effect_type="sparkle",
  25. colors={"dodger blue": "1E90FFFF"},
  26. quantity=2,
  27. ),
  28. TestSpool(
  29. effect_type="sparkle",
  30. colors={"dark red": "8B0000FF"},
  31. ),
  32. TestSpool(
  33. effect_type="wood",
  34. colors={"brown": "A47251FF"},
  35. ),
  36. TestSpool(
  37. effect_type="marble",
  38. colors={"slate": "4F5D75FF"},
  39. ),
  40. TestSpool(
  41. effect_type="glow",
  42. colors={"mint-pop": "6CD4BCFF"},
  43. ),
  44. TestSpool(
  45. effect_type="matte",
  46. colors={"charcoal": "2B2D42FF"},
  47. ),
  48. TestSpool(
  49. effect_type="silk",
  50. colors={"rose": "FF8FA3FF"},
  51. ),
  52. TestSpool(
  53. effect_type="galaxy",
  54. colors={"indigo": "4361EEFF"},
  55. ),
  56. TestSpool(
  57. effect_type="rainbow",
  58. colors={"amber": "FFBF69FF"},
  59. ),
  60. TestSpool(
  61. effect_type="metal",
  62. colors={"petrol-blue": "2D9CDBFF"},
  63. ),
  64. TestSpool(
  65. effect_type="translucent",
  66. colors={"cream": "FFF3E2AA"},
  67. ),
  68. TestSpool(
  69. effect_type="gradient",
  70. colors={
  71. "dark olive green": "556B2FFF",
  72. "goldenrod": "DAA520FF",
  73. },
  74. ),
  75. TestSpool(
  76. effect_type="dual-color",
  77. colors={
  78. "plum": "7B2CBFFF",
  79. "saffron": "F2C94CFF",
  80. },
  81. ),
  82. TestSpool(
  83. effect_type="tri-color",
  84. colors={
  85. "coral": "FF6B6BFF",
  86. "seafoam": "80ED99FF",
  87. "indigo": "4361EEFF",
  88. },
  89. ),
  90. TestSpool(
  91. effect_type="multicolor",
  92. colors={
  93. "sunset-orange": "EC984CFF",
  94. "mint-pop": "6CD4BCFF",
  95. "violet-bloom": "A66EB9FF",
  96. "raspberry-dawn": "D87694FF",
  97. },
  98. ),
  99. TestSpool(
  100. effect_type="multicolor",
  101. colors={
  102. "deep-navy": "1B1F3BFF",
  103. "cream": "FFF3E2FF",
  104. "rose": "FF8FA3FF",
  105. "teal": "2EC4B6FF",
  106. "amber": "FFBF69FF",
  107. },
  108. ),
  109. ]
  110. def build_spool_payload(variant: TestSpool) -> dict:
  111. "Function to build the payload for a single spool variant"
  112. color_values = list(variant.colors.values())
  113. color_names = list(variant.colors.keys())
  114. return {
  115. "material": "PLA",
  116. "subtype": variant.effect_type,
  117. "brand": "Generic",
  118. "color_name": ", ".join(color_names),
  119. "rgba": color_values[0],
  120. "extra_colors": ",".join(color_values),
  121. "effect_type": variant.effect_type,
  122. "label_weight": 1000,
  123. "core_weight": 250,
  124. "weight_used": 0,
  125. "core_weight_catalog_id": None,
  126. "slicer_filament": None,
  127. "slicer_filament_name": None,
  128. "nozzle_temp_min": None,
  129. "nozzle_temp_max": None,
  130. "note": "Dev effect overview seed",
  131. "cost_per_kg": None,
  132. "category": None,
  133. "low_stock_threshold_pct": None,
  134. }
  135. def create_bulk_spools(
  136. bambuddy_url: str,
  137. spool_data: dict,
  138. quantity: int,
  139. api_key: str | None,
  140. timeout: int,
  141. ) -> list[dict]:
  142. "Function that creates multiple spools using the bulk API endpoint and returns the list of created spools"
  143. url = f"{bambuddy_url.rstrip('/')}{API_PATH_BULK_CREATE}"
  144. headers: dict[str, str] = {}
  145. if api_key:
  146. headers["X-API-Key"] = api_key
  147. payload = {"spool": spool_data, "quantity": quantity}
  148. resp = requests.post(url, json=payload, headers=headers, timeout=timeout)
  149. resp.raise_for_status()
  150. data = resp.json()
  151. if not isinstance(data, list):
  152. raise ValueError("Bulk endpoint returned unexpected response format: expected a list of created spools")
  153. return data
  154. def main() -> None:
  155. parser = argparse.ArgumentParser(description="Create development stock spools for every effect type")
  156. parser.add_argument("--bambuddy-url", required=True, help="Bambuddy URL (e.g. http://localhost:8000)")
  157. parser.add_argument("--api-key", help="Bambuddy API key (required if auth is enabled)")
  158. parser.add_argument("--timeout", type=int, default=30, help="HTTP timeout in seconds (default: 30)")
  159. args = parser.parse_args()
  160. created = 0
  161. failed = 0
  162. for variant in SPOOLS:
  163. payload = build_spool_payload(variant)
  164. try:
  165. created_spools = create_bulk_spools(
  166. args.bambuddy_url,
  167. payload,
  168. quantity=variant.quantity,
  169. api_key=args.api_key,
  170. timeout=args.timeout,
  171. )
  172. ids = [str(item.get("id", "?")) for item in created_spools]
  173. print(f" Created effect={variant.effect_type:<11} qty={len(created_spools)} ids={','.join(ids)}")
  174. created += len(created_spools)
  175. except (requests.RequestException, ValueError) as exc:
  176. print(f" FAILED effect={variant.effect_type}: {exc}", file=sys.stderr)
  177. failed += variant.quantity
  178. print(f"\nDone: {created} created, {failed} failed")
  179. if __name__ == "__main__":
  180. main()