Spoolman leads CC2 Control's copies; new spools from the bridge
- Master data of CC2 Control's copies follows Spoolman (name, brand, colour, diameter, density, net, tare, price; not the material, which was fitted to the tray). - A loaded spool's rest in CC2 Control is set to Spoolman's via /api/spools/adjust once everything is booked (old copy after the roll printed in the other printer, weigh-in in Spoolman); the resulting correction is not booked back. - Copies of spools archived or deleted in Spoolman are deleted from CC2 Control when no tray holds them (library limit 128); an outage never counts as deleted. - A roll that ran out is archived in Spoolman (setting, default on); a scan of an archived spool maps nothing. - A roll scanned again with unchanged tag data and put back into its old slot (after printing in the other printer) is recognised. - Tag generator: "Neue Spule" creates spools in Spoolman from an existing or a new filament (and vendor), labelled vendor + id, then opens the tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GcEk5tkfFSYD6NTF66NkC
This commit is contained in:
1 parent
d03668e3c5
commit
b37cd64909
13 files changed
+873
-27
No files matched your search
+79
-3
@@ -44,7 +44,7 @@ def printer_state():
|
||||
|
||||
|
||||
PRINTERS: list[dict] = []
|
||||
STATE = {"spoolman": {}, "fields": {}, "requests": []}
|
||||
STATE = {"spoolman": {}, "fields": {}, "requests": [], "vendors": [], "filaments": []}
|
||||
LOCK = threading.Lock()
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ def seed_spoolman():
|
||||
"extra": {},
|
||||
"filament": {"id": sid, "name": f"{mat} {'Silk Gold' if sid == 14 else color}", "material": mat,
|
||||
"color_hex": color, "diameter": 1.75, "density": 1.24, "weight": 1000.0,
|
||||
"settings_extruder_temp": 215, "vendor": {"id": 1, "name": vendor}},
|
||||
"settings_extruder_temp": 215,
|
||||
"vendor": {"id": 1 if vendor == "Sunlu" else 2 if vendor == "Elegoo" else 3, "name": vendor}},
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +135,9 @@ def make_cc2(host_header: str, P: dict) -> FastAPI:
|
||||
for k in ("name", "brand", "material", "color", "note"):
|
||||
if k in f:
|
||||
sp[k] = f[k]
|
||||
for k in ("diameter", "density", "net", "tare", "low", "price"):
|
||||
if k in f:
|
||||
sp[k] = float(f[k])
|
||||
if "archived" in f:
|
||||
sp["archived"] = f["archived"] == "1"
|
||||
return {"ok": True, "id": sp["id"]}
|
||||
@@ -147,6 +151,33 @@ def make_cc2(host_header: str, P: dict) -> FastAPI:
|
||||
P["spools"].append(sp)
|
||||
return JSONResponse({"ok": True, "id": sp["id"]}, 201)
|
||||
|
||||
@app.post("/api/spools/adjust")
|
||||
async def adjust(request: Request):
|
||||
f = form(await request.body())
|
||||
with LOCK:
|
||||
STATE["requests"].append("POST adjust " + json.dumps(f))
|
||||
sp = next((s for s in P["spools"] if s["id"] == f.get("id")), None)
|
||||
if not sp:
|
||||
return JSONResponse({"ok": False, "error": "Unknown spool"}, 404)
|
||||
value = float(f["remaining"])
|
||||
P["log"].insert(0, {"time": int(time.time()), "spool": sp["id"], "slot": -1, "kind": "correction",
|
||||
"grams": value - sp["remaining"], "mm": 0.0, "job": "", "result": "set"})
|
||||
sp["remaining"] = value
|
||||
return {"ok": True, "remaining": value}
|
||||
|
||||
@app.post("/api/spools/delete")
|
||||
async def delete(request: Request):
|
||||
f = form(await request.body())
|
||||
with LOCK:
|
||||
STATE["requests"].append("POST delete " + json.dumps(f))
|
||||
if not any(s["id"] == f.get("id") for s in P["spools"]):
|
||||
return JSONResponse({"ok": False, "error": "Unknown spool"}, 404)
|
||||
P["spools"][:] = [s for s in P["spools"] if s["id"] != f["id"]]
|
||||
for s in P["slots"]:
|
||||
if s["spool"] == f["id"]:
|
||||
s["spool"] = ""
|
||||
return {"ok": True}
|
||||
|
||||
@app.post("/api/spools/assign")
|
||||
async def assign(request: Request):
|
||||
f = form(await request.body())
|
||||
@@ -189,7 +220,7 @@ def make_spoolman() -> FastAPI:
|
||||
for key, value in (body.get("extra") or {}).items():
|
||||
if value is not None and key not in STATE["fields"]:
|
||||
raise HTTPException(400, f"Unknown extra field {key}")
|
||||
sp.update({k: v for k, v in body.items() if k in ("location",)})
|
||||
sp.update({k: v for k, v in body.items() if k in ("location", "lot_nr", "archived")})
|
||||
if "used_weight" in body:
|
||||
sp["used_weight"] = float(body["used_weight"])
|
||||
sp["remaining_weight"] = (sp.get("initial_weight") or 1000.0) - sp["used_weight"]
|
||||
@@ -201,6 +232,51 @@ def make_spoolman() -> FastAPI:
|
||||
STATE["requests"].append(f"PATCH spoolman {sid} {json.dumps(body, ensure_ascii=False)}")
|
||||
return sp
|
||||
|
||||
@app.get("/api/v1/vendor")
|
||||
def vendors():
|
||||
found = {s["filament"]["vendor"]["id"]: s["filament"]["vendor"] for s in STATE["spoolman"].values()}
|
||||
found.update({v["id"]: v for v in STATE["vendors"]})
|
||||
return list(found.values())
|
||||
|
||||
@app.get("/api/v1/filament")
|
||||
def filaments():
|
||||
found = {s["filament"]["id"]: s["filament"] for s in STATE["spoolman"].values()}
|
||||
found.update({f["id"]: f for f in STATE["filaments"]})
|
||||
return list(found.values())
|
||||
|
||||
@app.post("/api/v1/vendor")
|
||||
async def add_vendor(request: Request):
|
||||
body = await request.json()
|
||||
with LOCK:
|
||||
v = {"id": max([x["id"] for x in vendors()], default=0) + 1, **body}
|
||||
STATE["vendors"].append(v)
|
||||
STATE["requests"].append(f"POST vendor {json.dumps(body, ensure_ascii=False)}")
|
||||
return v
|
||||
|
||||
@app.post("/api/v1/filament")
|
||||
async def add_filament(request: Request):
|
||||
body = await request.json()
|
||||
with LOCK:
|
||||
vendor = next(v for v in vendors() if v["id"] == body["vendor_id"])
|
||||
f = {"id": max([x["id"] for x in filaments()], default=0) + 1, "vendor": vendor, **body}
|
||||
STATE["filaments"].append(f)
|
||||
STATE["requests"].append(f"POST filament {json.dumps(body, ensure_ascii=False)}")
|
||||
return f
|
||||
|
||||
@app.post("/api/v1/spool")
|
||||
async def add_spool(request: Request):
|
||||
body = await request.json()
|
||||
with LOCK:
|
||||
fil = next(f for f in filaments() if f["id"] == body["filament_id"])
|
||||
sid = max(STATE["spoolman"], default=0) + 1
|
||||
weight = body.get("initial_weight") or fil.get("weight") or 1000.0
|
||||
STATE["spoolman"][sid] = {"id": sid, "lot_nr": None, "remaining_weight": weight, "used_weight": 0.0,
|
||||
"initial_weight": weight, "spool_weight": body.get("spool_weight"),
|
||||
"location": body.get("location", ""), "archived": False, "price": body.get("price"),
|
||||
"extra": {}, "filament": fil}
|
||||
STATE["requests"].append(f"POST spool {json.dumps(body, ensure_ascii=False)}")
|
||||
return STATE["spoolman"][sid]
|
||||
|
||||
@app.get("/api/v1/field/spool")
|
||||
def fields():
|
||||
return [{"key": k, "entity_type": "spool", **v} for k, v in STATE["fields"].items()]
|
||||
|
||||
Reference in new issue
Block a user