diff --git a/app/filamentdb.py b/app/filamentdb.py index e4ea1ea..a895063 100644 --- a/app/filamentdb.py +++ b/app/filamentdb.py @@ -1,5 +1,7 @@ import logging +import math import os +import re import time import httpx @@ -64,6 +66,70 @@ def spool_counts() -> dict[tuple[str, str], int]: return result +def _parse_ams_html(html: str) -> list[dict]: + """ + Parse the /ams/ HTML page to extract slot info. + Each card yields: slot, material, color, weight_remaining_g, roll_id (numeric). + """ + slots = [] + for card in re.split(r'class="card ams-card"', html)[1:]: + slot_m = re.search(r'

Slot (\d+)

', card) + matcol_m = re.search( + r'.*?
\s*(.*?)
\s*(.*?)\s*

', card, re.DOTALL + ) + weight_m = re.search(r'~(\d+)g', card) + roll_id_m = re.search(r'action="/rolls/(\d+)/prints"', card) + if slot_m and matcol_m and weight_m and roll_id_m: + slots.append({ + "slot": int(slot_m.group(1)), + "material": matcol_m.group(1).strip(), + "color": matcol_m.group(2).strip(), + "weight_remaining_g": int(weight_m.group(1)), + "roll_id": int(roll_id_m.group(1)), + }) + return slots + + +def get_ams_slots() -> list[dict]: + """Return currently loaded AMS slots (live, not cached — state changes often).""" + try: + r = httpx.get(f"{_base()}/ams/", timeout=5, verify=False) + r.raise_for_status() + return _parse_ams_html(r.text) + except Exception: + logger.warning("FilamentDB AMS endpoint unreachable") + return [] + + +def find_matching_ams_roll(material: str, color: str) -> dict | None: + """ + Find the best roll in AMS1/slots 1-4 matching material+color. + If multiple match, pick the one with least filament remaining (use it up first). + """ + matches = [ + s for s in get_ams_slots() + if s["material"].lower() == material.lower() + and s["color"].lower() == color.lower() + ] + return min(matches, key=lambda s: s["weight_remaining_g"]) if matches else None + + +def log_print_on_roll(roll_id: int, weight_g: int, note: str) -> bool: + """POST a print log entry to FilamentDB for the given roll.""" + try: + r = httpx.post( + f"{_base()}/rolls/{roll_id}/prints", + data={"weight_used_g": weight_g, "note": note, "redirect_to": ""}, + timeout=5, + verify=False, + ) + r.raise_for_status() + return True + except Exception: + logger.warning("FilamentDB print log failed for roll %s", roll_id) + return False + + def available_colors(material: str | None = None) -> list[str]: """Unique color names in stock, optionally filtered by material.""" rows = get_stock() diff --git a/app/routers/ui.py b/app/routers/ui.py index d8ae935..6cf9d2c 100644 --- a/app/routers/ui.py +++ b/app/routers/ui.py @@ -1,3 +1,4 @@ +import math import os import uuid as _uuid from datetime import datetime, timedelta @@ -21,8 +22,9 @@ from app.models.printer_type import PrinterType from app.models.pricing import PricingConfig from app.models.todo import Todo from app.constants import COLORS, MATERIALS -from app.estimation import estimate -from app.filamentdb import available_colors, available_materials, get_stock, spool_counts +from app.estimation import estimate # also used in start_job +from app.filamentdb import (available_colors, available_materials, find_matching_ams_roll, + get_stock, log_print_on_roll, spool_counts) from app.stl import get_stl_dir, list_stl_files from app.stl_analysis import analyse, get_cache_map # noqa: F401 used in job_detail route @@ -325,6 +327,29 @@ def start_job(job_id: int, printer_id: int = Form(...), db: Session = Depends(ge printer.status = PrinterStatus.printing db.add(JobLog(job_id=job_id, message=f"Job started on {printer.name}")) + + # Log filament usage against matching AMS roll in FilamentDB + if job.job_material and job.job_color: + stl = db.query(StlCache).filter_by(filename=job.file_name).first() if job.file_name else None + if stl and stl.volume_ccm: + weight_g = math.ceil(estimate(stl.volume_ccm, job.job_material)["weight_g"]) + roll = find_matching_ams_roll(job.job_material, job.job_color) + if roll: + note = (f"POPS {job.job_uuid[:8] if job.job_uuid else job.id}: " + f"{job.file_name or job.name} on {printer.name}") + if log_print_on_roll(roll["roll_id"], weight_g, note): + db.add(JobLog(job_id=job_id, message=( + f"FilamentDB: logged {weight_g} g against roll #{roll['roll_id']} " + f"(AMS slot {roll['slot']} — {roll['material']} {roll['color']}, " + f"{roll['weight_remaining_g']} g remaining, least-full roll selected)" + ))) + else: + db.add(JobLog(job_id=job_id, message=f"FilamentDB: print log failed for roll #{roll['roll_id']}")) + else: + db.add(JobLog(job_id=job_id, message=( + f"FilamentDB: no {job.job_material} {job.job_color} roll found in AMS" + ))) + db.commit() return RedirectResponse(url=f"/jobs/{job_id}", status_code=HTTP_303_SEE_OTHER)