Spaces:
Running on Zero
Running on Zero
Download agent_api.py from townbox/uniforge3d: direct link, hf CLI and curl.
- Browser
- Download file 6.12 kB
-
https://huggingface.co/spaces/townbox/uniforge3d/resolve/main/agent_api.py
- Command line
-
hf download hf://spaces/townbox/uniforge3d/agent_api.py
-
curl -L -o agent_api.py https://huggingface.co/spaces/townbox/uniforge3d/resolve/main/agent_api.py
6.12 kB
| """What the API documents for agents: /gradio_api/info, which the Space's agents.md points to. | |
| - texture_size: Gradio's clients render every enum as strings (python_type Literal['1024', '2048'], | |
| "2048" in the code snippets), so the documented choices are the strings "1024" and "2048". | |
| The numbers 1024 and 2048 are accepted too. | |
| - Endpoint descriptions: parameters, timing, auth, and where the GLB is in the result. | |
| - Code snippets: the bash one is generated with the public host (Gradio uses the host of the | |
| first /info request, which on Spaces is often http://localhost:7860), sends the HF token, and | |
| keeps EVENT_ID in bash (Gradio's `| read EVENT_ID` runs in a subshell there, so it is empty). | |
| """ | |
| import os | |
| import re | |
| import gradio as gr | |
| import gradio.routes | |
| TEXTURE_SIZES = ("1024", "2048") | |
| class TextureSize(gr.Radio): | |
| """The texture size radio (create it with choices TEXTURE_SIZES); passes the size to the | |
| function as an int. No __init__ of its own: Gradio builds a component's config from its | |
| constructor's parameters.""" | |
| is_template = True # rendered by the frontend as a gr.Radio | |
| def preprocess(self, payload): | |
| if payload is None: | |
| payload = self.value | |
| if isinstance(payload, (int, float)) and not isinstance(payload, bool) and float(payload).is_integer(): | |
| payload = str(int(payload)) | |
| elif isinstance(payload, str): | |
| payload = payload.strip() | |
| if payload not in TEXTURE_SIZES: | |
| raise gr.Error(f'texture_size must be "1024" or "2048" (got {payload!r}).') | |
| return int(super().preprocess(payload)) | |
| def api_info(self): | |
| return { | |
| **super().api_info(), | |
| "description": 'texture resolution in pixels: "1024" or "2048" (the numbers 1024 and 2048 work too)', | |
| } | |
| # name: (steps, faces, texture_size, end-to-end time). Times measured on the Space (2026-10-16) for | |
| # a rocking chair, a bonsai and an espresso machine; detailed objects take the longer times. | |
| PRESETS = { | |
| "Fast": (8, 100_000, "1024", "1-3 min"), | |
| "Balanced": (12, 300_000, "2048", "1.5-4 min"), | |
| "Quality": (20, 300_000, "2048", "1.5-5.5 min"), | |
| } | |
| _COMMON = ( | |
| "faces: 100000-500000, default 300000. " | |
| 'texture_size: "1024" or "2048", default "2048". ' | |
| "steps: 8-20, default 12 (20 is the paper setting, slower). " | |
| "Presets, each verified end to end: " | |
| + "; ".join( | |
| f'{name.lower()}: steps {steps}, faces {faces}, texture_size "{texture}" ({time})' | |
| for name, (steps, faces, texture, time) in PRESETS.items() | |
| ) | |
| + ". Balanced is the default; detailed objects take the longer times. " | |
| "Runs use the caller's ZeroGPU quota, so send your " | |
| "Hugging Face token (Authorization: Bearer $HF_TOKEN). " | |
| "Poll GET /gradio_api/call/{endpoint}/{event_id}: it streams `generating` updates for the web page, then " | |
| "`event: complete` with data = [progress HTML, 3D viewer state, GLB file, seed used]. " | |
| "Download the model from data[2].url. A failure ends the stream with `event: error` and a message." | |
| ) | |
| TEXT_API = ( | |
| "Text to a textured 3D model (GLB): Krea 2 draws the object, Qwen-Image-Edit-2511 generates its " | |
| "right, back and left views, and UniForge3D builds the model from all four. " | |
| 'subject (required): the object, e.g. "a red vintage rotary telephone". ' | |
| "seed: integer, used only when randomize is false. randomize: default true. " + _COMMON | |
| ) | |
| IMAGE_API = ( | |
| "One image of a single object to a textured 3D model (GLB): the background is removed " | |
| "automatically, Qwen-Image-Edit-2511 generates the right, back and left views, and UniForge3D " | |
| "builds the model from all four. " | |
| "image (required): a photo or render of the object seen from the front (PNG, JPEG or WebP). " | |
| "Upload it with POST /gradio_api/upload -F files=@object.jpg and pass " | |
| '{"path": "<returned path>", "meta": {"_type": "gradio.FileData"}}, or pass a public image URL ' | |
| 'as {"path": "https://...", "meta": {"_type": "gradio.FileData"}}. ' | |
| "seed: integer, default 42, used only when randomize is false. randomize: default false. " + _COMMON | |
| ) | |
| AUTH = '-H "Authorization: Bearer $HF_TOKEN"' | |
| _BASH_CALL = re.compile( | |
| r'curl -X POST (?P<post>\S+) -s -H "Content-Type: application/json" \\\n' | |
| r"(?P<data> -d '.*') \\\n" | |
| r" \| awk -F'\"' '\{ print \$4\}' \\\n" | |
| r" \| read EVENT_ID; curl -N (?P<get>\S+)$" | |
| ) | |
| def _fix_bash(snippet): | |
| match = _BASH_CALL.search(snippet) | |
| if not match: # a Gradio version with another template: keep it | |
| return snippet | |
| call = ( | |
| f'EVENT_ID=$(curl -s -X POST {match["post"]} -H "Content-Type: application/json" {AUTH} \\\n' | |
| f'{match["data"]} \\\n' | |
| " | awk -F'\"' '{ print $4}')\n" | |
| f'curl -sS -N {AUTH} {match["get"]}' | |
| ) | |
| snippet = snippet[: match.start()] + call | |
| return snippet.replace("FILE_PATH=$(curl -s -X POST ", f"FILE_PATH=$(curl -s {AUTH} -X POST ", 1) | |
| def document_only(blocks, endpoints): | |
| """Keep the API docs to `endpoints`. While gr.Examples caches, Gradio registers a public | |
| endpoint named after the example function, and /info is computed once, on the first request, | |
| so whether such endpoints were documented depended on timing.""" | |
| get_api_info = blocks.get_api_info | |
| def api_info(all_endpoints=False): | |
| info = get_api_info(all_endpoints) | |
| if not all_endpoints: | |
| info["named_endpoints"] = {k: v for k, v in info["named_endpoints"].items() if k in endpoints} | |
| info["unnamed_endpoints"] = {} | |
| return info | |
| blocks.get_api_info = api_info | |
| _generate_code_snippets = gradio.routes.generate_code_snippets | |
| def _code_snippets(api_name, endpoint_info, root, space_id=None, api_prefix="/"): | |
| host = os.environ.get("SPACE_HOST") | |
| if host: | |
| root = f"https://{host}" | |
| snippets = _generate_code_snippets(api_name, endpoint_info, root, space_id=space_id, api_prefix=api_prefix) | |
| snippets["bash"] = _fix_bash(snippets["bash"]) | |
| return snippets | |
| gradio.routes.generate_code_snippets = _code_snippets | |