mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
#!/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()
|