diff --git a/server.py b/server.py index d7bf6eb..cce6f3d 100644 --- a/server.py +++ b/server.py @@ -320,6 +320,11 @@ TEMPLATES = { 'no coat hanger, no text, no logos'), 'doll2d_green': ('1990s Australian {phrase}, product photo, flat lay, centered, ' 'solid bright green background, no text, no words, no logos, no person, no hanger'), + # rigid props/hats/bags headed for TRELLIS: three-quarter view reconstructs far better than + # a flat lay, and 'op-shop garment slightly worn' is wrong for a hard hat or a vinyl record. + 'object3d': ('product photo of a single {phrase}, three-quarter view, centered, floating on a ' + 'plain white background, studio lighting, sharp focus, entire object visible, ' + 'no person, no hands, no mannequin, no text, no logos'), } VIEWS = {'hat': 'front view', 'top': 'front view laid flat with the sleeves down', 'bottom': 'front view laid perfectly flat on the ground', diff --git a/tools/factory.py b/tools/factory.py new file mode 100644 index 0000000..46fc4bb --- /dev/null +++ b/tools/factory.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +"""wardrobegod batch factory — build-first catalogue items, serially, via the +live wardrobegod API. Heartbeat at ~/.jobs/wardrobe-batch1.json. + +Per item: /api/gen (klein on the farm) -> /api/rmbg (RMBG-2.0 cutout) -> +[3D items] /api/to3d (TRELLIS, ~5 min serial GPU lane). +Cutout PNGs and GLBs land in library/ automatically — that is the point of +driving the product API instead of the farm directly. +""" +import json, os, time, urllib.request + +API = "http://100.91.239.7:8150" +HB = os.path.expanduser("~/.jobs/wardrobe-batch1.json") +os.makedirs(os.path.dirname(HB), exist_ok=True) + +# id, phrase, template, view, to3d? +ITEMS = [ + # --- R2 rigid props/hats -> full 3D chain --- + ("cap-baseball-fwd", "faded navy blue baseball cap", "object3d", "hat", True), + ("hardhat-construction", "yellow construction hard hat", "object3d", "hat", True), + ("headphones-dj-overear","black over-ear DJ headphones with a coiled cable", "object3d", "hat", True), + ("record-vinyl-12in", "12 inch black vinyl record with a plain orange centre label", "object3d", "hat", True), + ("sunglasses-wayfarer-generic", "black chunky rectangular-frame sunglasses", "object3d", "hat", True), + ("bag-record-crossbody", "black canvas record bag with a wide shoulder strap", "object3d", "bag", True), + ("shopping-basket", "red plastic shopping basket with two metal handles", "object3d", "bag", True), + ("sneaker-low-canvas", "pair of plain white low-top canvas sneakers", "object3d", "shoes", True), + ("boot-work-steelcap", "pair of tan leather steel-cap work boots", "object3d", "shoes", True), + ("chain-necklace-curb", "chunky silver curb chain necklace laid in a circle", "object3d", "hat", True), + # --- R1 garment cutouts (gen + rmbg only) --- + ("tee-plain-white", "plain white crew-neck t-shirt", "flat3d", "top", False), + ("tee-print-smiley", "black t-shirt with a large orange smiley face print", "flat3d", "top", False), + ("tee-print-sunset", "white t-shirt with a retro sunset stripes print", "flat3d", "top", False), + ("hivis-vest-yellow", "yellow hi-vis safety vest with silver reflective stripes", "flat3d", "top", False), + ("tights-opaque-black", "pair of black opaque tights", "flat3d", "bottom", False), + ("shorts-denim-cutoff", "light blue denim cutoff shorts", "flat3d", "bottom", False), +] + + +def api(path, payload=None, timeout=30): + if payload is None: + req = urllib.request.Request(API + path) + else: + req = urllib.request.Request(API + path, data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}) + with urllib.request.urlopen(req, timeout=timeout) as r: + return json.load(r) + + +def wait(jid, timeout=1500): + t0 = time.time() + while time.time() - t0 < timeout: + j = api(f"/api/job/{jid}") + if j.get("status") in ("done", "error"): + return j + time.sleep(6) + return {"status": "error", "log": "poll timeout"} + + +def hb(state): + state["updated"] = time.strftime("%Y-%m-%d %H:%M:%S") + json.dump(state, open(HB, "w"), indent=1) + + +results = [] +state = {"job": "wardrobe-batch1", "status": "running", "total": len(ITEMS), + "done": 0, "failed": 0, "current": "", "results": results} +hb(state) + +for i, (iid, phrase, tpl, view, want3d) in enumerate(ITEMS): + row = {"id": iid, "steps": {}} + results.append(row) + state["current"] = iid + hb(state) + try: + g = api("/api/gen", {"phrase": phrase, "template": tpl, "view": view, + "backend": "klein"}) + j = wait(g["job"], 300) + row["steps"]["gen"] = j.get("status") + if j.get("status") != "done": + raise RuntimeError("gen: " + str(j.get("log", ""))[:200]) + png = j["out"] + + c = api("/api/rmbg", {"path": png}) + j = wait(c["job"], 300) + row["steps"]["rmbg"] = j.get("status") + if j.get("status") != "done": + raise RuntimeError("rmbg: " + str(j.get("log", ""))[:200]) + cut = j["out"] + row["cut"] = cut + + if want3d: + t = api("/api/to3d", {"path": cut}) + j = wait(t["job"], 1500) + row["steps"]["to3d"] = j.get("status") + if j.get("status") != "done": + raise RuntimeError("to3d: " + str(j.get("log", ""))[:200]) + row["glb"] = j.get("out") + state["done"] += 1 + except Exception as e: + row["error"] = str(e)[:300] + state["failed"] += 1 + hb(state) + +state["status"] = "done" +state["current"] = "" +hb(state) +print(json.dumps(state, indent=1))