#!/usr/bin/env python3 """fal.ai Hunyuan3D v2 image->GLB for the two TRELLIS glass rejects (glass-case, bus-shelter). Paid — John-authorized (ROUND5 decision #4; account topped up). ~14-17c/gen, <=2 attempts each, every gen logged to AUDIT.md by the caller. Uses the owner's FAL_KEY configured in MODELBEAST settings (never hardcoded here) via MODELBEAST's own settings loader — the same env-injection the server does. MUST run with MODELBEAST's server venv python (it has fal_client): MBPY=~/Documents/MODELBEAST/.venv/bin/python $MBPY pipeline/gen_props_fal.py CONCEPT.png OUT.glb Prints `FAL_OK ~$0.16` on success (cost is the caller's to record), `FAL_ERR ...` on failure. """ import sys, os, subprocess, json, glob, shutil MB = os.path.expanduser("~/Documents/MODELBEAST") sys.path.insert(0, MB) from server import db, settings as S # noqa: E402 ENDPOINT = "fal-ai/hunyuan3d/v2" # ~$0.16 geo (+ texture); see MESHGOD_BATCH.md pricing COST = 0.16 def main(): concept, out = sys.argv[1], sys.argv[2] con = db.connect() env = os.environ.copy() env.update(S.env_for_operator(S.get_all(con), ["FAL_KEY"])) if not env.get("FAL_KEY"): print("FAL_ERR FAL_KEY not configured in MODELBEAST settings"); sys.exit(2) opdir = os.path.join(MB, "server/operators/fal_hunyuan3d") outdir = os.path.join(os.path.dirname(out) or ".", "_fal_" + os.path.basename(out).replace(".glb", "")) os.makedirs(outdir, exist_ok=True) params = json.dumps({"seed": 0, "textured_mesh": True}) r = subprocess.run([sys.executable, os.path.join(opdir, "run.py"), "--input", concept, "--outdir", outdir, "--params", params], env=env, capture_output=True, text=True, timeout=600) glbs = glob.glob(os.path.join(outdir, "**", "*.glb"), recursive=True) if not glbs: print("FAL_ERR " + (r.stderr or r.stdout or "no glb")[-300:]); sys.exit(1) shutil.move(sorted(glbs)[0], out) shutil.rmtree(outdir, ignore_errors=True) print(f"FAL_OK {out} ~${COST:.2f} ({ENDPOINT})") if __name__ == "__main__": main()