--- title: projtex · Projection Texturing API emoji: 🎨 colorFrom: green colorTo: yellow sdk: docker app_port: 7860 pinned: false license: mit short_description: Agent-first API to texture 3D meshes with Qwen-Image / Fibo models: - Qwen/Qwen-Image-2.1-Turbo - Qwen/Qwen-Image-2.1 - briaai/Fibo-2.0-edit-turbo tags: - 3d - texturing - mcp-server - agents - image-editing --- # projtex: projection texturing API (agent-first) Send a 3D mesh and a prompt; get back the mesh with a baked colour texture (GLB). The service: 1. generates images from virtual cameras, each conditioned on what is already painted; 2. checks every image against the geometry; 3. projects the images onto the mesh and blends them in texture space; 4. bakes the result into the mesh's UVs. Three image models are available, through Hugging Face Spaces: - **Qwen-Image-2.1-Turbo** (8 steps) via [hugging-apps/qwen-qwen-image-2-1-turbo](https://huggingface.co/spaces/hugging-apps/qwen-qwen-image-2-1-turbo) (default: fastest, least GPU quota); - **Qwen-Image-2.1** (full model, 40 steps) via the official [Qwen/Qwen-Image-2.1](https://huggingface.co/spaces/Qwen/Qwen-Image-2.1) (`"backend": "qwen-space"`); - **Bria Fibo 2.0 Edit Turbo** via [briaai/Fibo-2.0-editing](https://huggingface.co/spaces/briaai/Fibo-2.0-editing) (`"backend": "fibo-space"`). Designed for agents: * **Asynchronous jobs.** `POST /api/jobs` returns at once. Poll `GET /api/jobs/{id}`; every status includes `progress`, quality `summary`, file URLs and a `next` list of concrete follow-up calls. * **Three equivalent interfaces:** * REST with OpenAPI at [`/docs`](/docs); * remote **MCP** (streamable HTTP) at `/mcp`; * an agent guide at [`/llms.txt`](/llms.txt). * **Inputs agents can produce:** a local file uploaded as raw bytes (`POST /api/uploads` -> `upload_id`, then a plain JSON job), `mesh_base64`, a public `mesh_url`, or a built-in `sample`. GLB from Blender or three.js (`GLTFExporter`), glTF with embedded data, OBJ, PLY, STL or a zip. * **Outputs that drop back into your scene:** the GLB keeps your objects (names, transforms), materials (names, metallic/roughness/alpha) and orientation; every material's base colour uses one baked texture. When your UVs are kept, the albedo PNG also fits your original model. Inside-out surfaces are detected and flipped. * **Honest progress and quality:** queue position and ETA while waiting; a `verdict` (good / usable / needs work) with coverage measured over what cameras can see (the underside is reported apart). * **A quality loop as API calls:** regenerate views, disable views, fill gaps, local retouch (repair / replace / upscale), pick 3D points from pixels, and re-bake. * **Free dry runs** with `"backend": "mock"`, and **bring-your-own images** with `"backend": "external"`. * **Flat errors** `{"error", "hint"}`. ## Quick start ```bash BASE=https://townbox-projection-texturing.hf.space curl -s --data-binary @house.glb -H 'content-type: model/gltf-binary' $BASE/api/uploads # -> {"upload_id": "up_...", ...} curl -s -X POST $BASE/api/jobs -H 'content-type: application/json' -H "X-HF-Token: $HF_TOKEN" \ -d '{"upload_id":"up_...","prompt":"white clapboard siding, grey shingle roof; dark green door"}' # -> {"job_id": "...", "state": "queued", "poll_after_seconds": 15, "next": [...]} curl -s $BASE/api/jobs/ # when "state" is "done": files.textured_glb, files.contact_sheet, summary.verdict, output, next ``` Try it without a mesh: `{"sample": "lantern", "prompt": "black wrought iron, weathered oak post, warm glass"}`. Terrain and ground: `POST /api/terrain {"upload_id": "up_...", "prompt": "an alpine valley in early summer"}` (or a `heightmap`, and your own `layers`) returns `files.terrain_glb` (the ground with a top-down colour map) and `files.bundle_zip` (splat maps, seamless detail textures, `blender_terrain.py`, `three_terrain.js`). See the "Terrain and ground" section of /llms.txt. PBR materials: add `"pbr": true` to a texture job (or POST /api/jobs/{id}/pbr afterwards) for `files.textured_pbr_glb` with a de-lit base colour and a metallicRoughness texture (roughness, metallic maps from a LoRA on Qwen-Image-Edit-2511, run on the caller's ZeroGPU quota). MCP client configuration: ```json {"mcpServers": {"projtex": {"type": "http", "url": "https://townbox-projection-texturing.hf.space/mcp", "headers": {"X-HF-Token": "hf_..."}}}} ``` Tools: * `texture_mesh`, `texture_terrain`, `get_job` (with a preview image) and `view_image`; * the quality loop: `regenerate_views`, `disable_views`, `fill_gaps`, `retouch`, `undo_retouch`, `bake`, `pick_point`; * `submit_view_image` (bring your own images); * `service_info` and `guide`. ## Quota: who pays for generation Image generation runs on ZeroGPU Spaces and spends the **caller's** daily GPU quota, about 30 s per view. * **Your token:** send it as `X-HF-Token` (or the `hf_token` field) so your own quota is used. It stays in memory for that job only and is never written to disk, logged or returned. * **No token:** the job runs as the visitor identity the Hub forwards, if present, otherwise as the shared anonymous quota (about 2 GPU-minutes per day). `GET /api/identity` shows which applies to your requests. * **No quota needed:** the `mock` and `external` backends. ## What runs here The CPU part of [projtex](https://huggingface.co/spaces/townbox/projection-texturing/tree/main/projtex) runs in this container, with software OpenGL: * mesh import and UV repair; * camera planning (objects, characters with face close-ups, 360° environment probes); * validation of every generated view (rejects re-framed outputs); * texel-space fusion weighted by angle and depth; * push-pull hole filling, leave-one-out consistency checks, local retouch layers; * GLB export. Limits: * meshes: 60 MB, 600k triangles; * texture size: 1024, 2048 or 4096; * 8 operations run at once (the CPU-heavy parts share 2 CPU slots), the rest are queued; * jobs expire after 12 h or when the Space restarts. ## Licences * Code: MIT. * Qwen-Image-2.1 and Qwen-Image-2.1-Turbo outputs: Qwen research licence. * Fibo 2.0 Edit Turbo outputs: CC BY-NC 4.0 (non-commercial). * Samples: Avocado and Lantern are CC0 (Khronos glTF-Sample-Assets). The head is the Lee Perry-Smith scan, CC-BY 3.0, by Infinite-Realities. Inspired by the projection-texturing workflow shown by @alexfredo87. This is an independent implementation.