UltraTex-Qwen-Image-2.1 / code /tools /trellis_generate.py
sekkit's picture orwelian84's picture
Duplicate from orwelian84/UltraTex-Qwen-Image-2.1
cf8a54e
Raw History Blame Contribute Delete
3.72 kB
#!/usr/bin/env python
"""Generate a mesh from a reference image through the local TRELLIS Studio API.
python tools/trellis_generate.py --image concept.png --name fox --out /dir [--resolution 1024] [--gpu John]
Uploads the image, queues one `generate` job, polls /api/state until it
completes, downloads the asset GLB to <out>/<name>.glb and prints its path.
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import time
import urllib.request
import uuid
API = os.environ.get("TRELLIS_API", "http://127.0.0.1:8785")
def multipart(path: str):
boundary = uuid.uuid4().hex
name = os.path.basename(path)
with open(path, "rb") as f:
data = f.read()
body = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{name}\"\r\n"
f"Content-Type: application/octet-stream\r\n\r\n").encode() + data + f"\r\n--{boundary}--\r\n".encode()
return body, f"multipart/form-data; boundary={boundary}"
def call(path: str, data: bytes | None = None, content_type: str | None = None, method: str | None = None):
req = urllib.request.Request(API + path, data=data, method=method or ("POST" if data is not None else "GET"))
if content_type:
req.add_header("Content-Type", content_type)
with urllib.request.urlopen(req, timeout=120) as r:
return json.loads(r.read().decode())
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--image", required=True)
ap.add_argument("--name", required=True)
ap.add_argument("--out", required=True)
ap.add_argument("--resolution", type=int, default=1024, choices=[512, 1024, 1536])
ap.add_argument("--seed", type=int, default=42)
ap.add_argument("--steps", type=int, default=12)
ap.add_argument("--texture-size", type=int, default=2048)
ap.add_argument("--decimation", type=int, default=250000)
ap.add_argument("--gpu", default="auto")
ap.add_argument("--timeout", type=int, default=3600)
args = ap.parse_args()
body, ctype = multipart(args.image)
upload = call("/api/uploads", body, ctype)
print("uploaded", upload.get("id"), upload.get("kind"))
req = {"kind": "generate", "image_id": upload["id"], "resolution": args.resolution, "seed": args.seed, "steps": args.steps,
"texture_size": args.texture_size, "decimation_target": args.decimation, "gpu": args.gpu, "name": args.name, "variants": 1}
jobs = call("/api/jobs", json.dumps(req).encode(), "application/json")["jobs"]
job_id = jobs[0]["id"]
print("job", job_id, jobs[0].get("status"))
deadline = time.time() + args.timeout
last = None
while time.time() < deadline:
state = call("/api/state")
job = next((j for j in state["jobs"] if j["id"] == job_id), None)
if job is None:
raise SystemExit("job vanished")
stamp = (job.get("status"), job.get("stage"), job.get("progress"))
if stamp != last:
print(" ", *stamp, flush=True)
last = stamp
if job["status"] == "completed":
asset = next((a for a in state["assets"] if a.get("job_id") == job_id), None)
if asset is None:
raise SystemExit("completed but no asset recorded")
os.makedirs(args.out, exist_ok=True)
dst = os.path.join(args.out, f"{args.name}.glb")
urllib.request.urlretrieve(API + asset["url"], dst)
print("TRELLIS_OK", dst)
return
if job["status"] in ("failed", "cancelled"):
raise SystemExit(f"job {job['status']}: {job.get('error')}")
time.sleep(10)
raise SystemExit("timed out")
if __name__ == "__main__":
main()