| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- #!/usr/bin/env python3
- """Import spools from Spoolman into Bambuddy inventory.
- Usage:
- python scripts/import_spoolman.py --spoolman-url http://localhost:7912 --bambuddy-url http://localhost:8000
- python scripts/import_spoolman.py --spoolman-url http://localhost:7912 --bambuddy-url http://localhost:8000 --api-key YOUR_KEY
- python scripts/import_spoolman.py --spoolman-url http://localhost:7912 --bambuddy-url http://localhost:8000 --dry-run
- """
- import argparse
- import sys
- import requests
- def fetch_spoolman_spools(spoolman_url: str) -> list[dict]:
- """Fetch all spools from Spoolman API."""
- url = f"{spoolman_url.rstrip('/')}/api/v1/spool"
- resp = requests.get(url, timeout=30)
- resp.raise_for_status()
- return resp.json()
- def map_spool(sm_spool: dict) -> dict:
- """Map a Spoolman spool to a Bambuddy SpoolCreate payload."""
- filament = sm_spool.get("filament") or {}
- vendor = filament.get("vendor") or {}
- material = filament.get("material") or "PLA"
- color_hex = filament.get("color_hex") or ""
- # Spoolman color_hex is 6-char (#RRGGBB or RRGGBB), Bambuddy rgba is 8-char RRGGBBAA
- rgba = None
- if color_hex:
- color_hex = color_hex.lstrip("#")
- if len(color_hex) == 6:
- rgba = f"{color_hex}FF"
- elif len(color_hex) == 8:
- rgba = color_hex
- label_weight = int(filament.get("weight") or 1000)
- used_weight = float(sm_spool.get("used_weight") or 0)
- # Filament name from Spoolman (e.g. "eSun PLA+ Black")
- filament_name = filament.get("name") or ""
- # Vendor name (e.g. "eSun", "Bambu Lab")
- brand = vendor.get("name")
- # Color name - prefer filament color name if present
- color_name = filament.get("color_hex_name") or None
- # Cost: Spoolman stores price per spool, we need cost per kg
- cost_per_kg = None
- spool_price = sm_spool.get("price") or filament.get("price")
- if spool_price and label_weight > 0:
- cost_per_kg = round(float(spool_price) / (label_weight / 1000), 2)
- # Temperature range from filament settings
- nozzle_temp_min = filament.get("settings", {}).get("nozzle_temperature_min") if filament.get("settings") else None
- nozzle_temp_max = filament.get("settings", {}).get("nozzle_temperature_max") if filament.get("settings") else None
- # Extra fields
- extra = sm_spool.get("extra") or {}
- tag_uid = extra.get("tag") or None
- # Build note with Spoolman reference
- note_parts = []
- if sm_spool.get("comment"):
- note_parts.append(sm_spool["comment"])
- if sm_spool.get("lot_nr"):
- note_parts.append(f"Lot: {sm_spool['lot_nr']}")
- note_parts.append(f"Imported from Spoolman (ID: {sm_spool['id']})")
- note = " | ".join(note_parts)
- payload = {
- "material": material,
- "color_name": color_name,
- "rgba": rgba.upper() if rgba else None,
- "brand": brand,
- "label_weight": label_weight,
- "weight_used": used_weight,
- "note": note,
- "cost_per_kg": cost_per_kg,
- "tag_uid": tag_uid,
- "data_origin": "spoolman",
- }
- if filament_name:
- payload["subtype"] = filament_name
- if nozzle_temp_min is not None:
- payload["nozzle_temp_min"] = int(nozzle_temp_min)
- if nozzle_temp_max is not None:
- payload["nozzle_temp_max"] = int(nozzle_temp_max)
- return payload
- def create_bambuddy_spool(bambuddy_url: str, spool_data: dict, api_key: str | None = None) -> dict:
- """Create a spool in Bambuddy inventory."""
- url = f"{bambuddy_url.rstrip('/')}/api/v1/inventory/spools"
- headers = {}
- if api_key:
- headers["X-API-Key"] = api_key
- resp = requests.post(url, json=spool_data, headers=headers, timeout=30)
- resp.raise_for_status()
- return resp.json()
- def main():
- parser = argparse.ArgumentParser(description="Import spools from Spoolman into Bambuddy inventory")
- parser.add_argument("--spoolman-url", required=True, help="Spoolman URL (e.g. http://localhost:7912)")
- parser.add_argument("--bambuddy-url", required=True, help="Bambuddy URL (e.g. http://localhost:8000)")
- parser.add_argument("--api-key", help="Bambuddy API key (required if auth is enabled)")
- parser.add_argument("--dry-run", action="store_true", help="Print mapped spools without importing")
- parser.add_argument("--archived", action="store_true", help="Include archived Spoolman spools")
- args = parser.parse_args()
- print(f"Fetching spools from {args.spoolman_url}...")
- try:
- sm_spools = fetch_spoolman_spools(args.spoolman_url)
- except requests.RequestException as e:
- print(f"Error fetching from Spoolman: {e}", file=sys.stderr)
- sys.exit(1)
- if not args.archived:
- sm_spools = [s for s in sm_spools if not s.get("archived")]
- print(f"Found {len(sm_spools)} spools in Spoolman")
- if not sm_spools:
- print("Nothing to import.")
- return
- created = 0
- failed = 0
- for sm_spool in sm_spools:
- filament = sm_spool.get("filament") or {}
- vendor = (filament.get("vendor") or {}).get("name", "?")
- name = filament.get("name") or filament.get("material") or "Unknown"
- label = f"#{sm_spool['id']} {vendor} {name}"
- payload = map_spool(sm_spool)
- if args.dry_run:
- print(f" [DRY RUN] {label}")
- for k, v in payload.items():
- if v is not None:
- print(f" {k}: {v}")
- print()
- continue
- try:
- result = create_bambuddy_spool(args.bambuddy_url, payload, args.api_key)
- print(f" Imported {label} -> Bambuddy spool #{result['id']}")
- created += 1
- except requests.RequestException as e:
- print(f" FAILED {label}: {e}", file=sys.stderr)
- failed += 1
- if not args.dry_run:
- print(f"\nDone: {created} imported, {failed} failed")
- if __name__ == "__main__":
- main()
|