3D generation Spaces
Building a Space whose output is a 3D asset — image (occasionally text) in, downloadable and in-browser-viewable mesh (GLB/OBJ/PLY/STL) or gaussian splat (.ply/.splat) out. Read this whenever the user wants a Space/demo/playground for a 3D generation model, whether a stock checkpoint or their own finetuned variant. NeRF pipelines and world/scene models (HunyuanWorld, HY-World) are not covered.
The standard workflow in SKILL.md applies (create → build → iterate → verify). This file is the 3D entry point; the sub-references below cover the 3D-specific deltas.
Sub-references
| When to read | File |
|---|---|
| CUDA/C++ extension handling — prebuilt wheels, startup-compile tricks, the dominant failure mode | [3d-cuda-extensions.md](3d-cuda-extensions.md) |
| Output formats (GLB/OBJ/PLY/STL, splats), viewers, orientation, preprocessing, temp-file patterns | [3d-outputs.md](3d-outputs.md) |
| Model selection details and per-family recipes (TRELLIS, Hunyuan3D, TripoSR, SF3D, …) | [3d-models.md](3d-models.md) |
| Gaussian-splat deliverables (TripoSplat, LGM) — pure-PyTorch splat decode | [3d-gsplat.md](3d-gsplat.md) |
What makes 3D Spaces different
- Native extensions. These models need CUDA/C++ extensions (nvdiffrast, rasterizers, marching cubes, texture bakers) that cannot compile at Space build time. Working Spaces use prebuilt wheels or startup-compile tricks —
3d-cuda-extensions.md. Getting this wrong is the dominant failure mode. - Vendored model code. None of the major 3D models are cleanly pip-installable; every official Space carries the model library as a source tree in the Space repo. There is no
diffusers-style one-liner. (Don't trust PyPI lookalikes:trellis-3dships the Python tree without the CUDA extensions or the real dependency set.)
Both points make "duplicate the official Space, then edit" beat "assemble from scratch" here more than for any other model class.
Picking a model
| Priority | Pick | Why | Reference |
|---|---|---|---|
| Best quality, PBR textures | TRELLIS.2 | current quality bar; MIT; ≥24 GB (fits ZeroGPU large) | 3d-models.md |
| Textured output, finetuning story | Hunyuan3D-2.1 (2.0 for lighter/faster) | official training code → common finetune target; PBR paint stage; non-commercial license | 3d-models.md |
| Speed / high-traffic / simplicity | TripoSR (untextured, MIT) or SF3D (textured, gated) | single forward pass, ~60s duration, tiny codebase | 3d-models.md |
| Gaussians alongside mesh, multi-image input | TRELLIS 1 | dual gaussian+mesh decoder; lighter than TRELLIS.2 | 3d-models.md |
| Gaussian splats as the deliverable | TripoSplat (or LGM) | pure-PyTorch splat decode, no rasterizer needed; gr.Model3D renders splat .ply natively |
3d-gsplat.md |
Check gating and license in Phase 0, not at first build. stabilityai/stable-fast-3d and stable-point-aware-3d are gated (gated: auto) — the user must have accepted the license and the Space needs HF_TOKEN as a secret; verify access with hf repos info / HfApi().model_info() up front. tencent/Hunyuan3D-* is ungated but non-commercial — flag before the Space goes public. TRELLIS/TripoSR/TripoSplat/LGM are MIT. Apple SHARP is research-only (apple-amlr).
If the model isn't covered by a reference file (Direct3D, TripoSG, Step1X-3D, a new release…), proceed by analogy with the user's agreement: find its official/most-liked working Space, fetch its actual files, and apply the same analysis — never guess a 3D dependency stack from memory.
Deployment path: duplicate vs build
Duplicate the official Space (default for stock checkpoints, and for finetunes that are a from_pretrained swap — each reference file documents the swap):
from huggingface_hub import HfApi
api = HfApi()
api.duplicate_repo("tencent/Hunyuan3D-2", to_id=f"{username}/my-hunyuan3d",
repo_type="space", private=True, space_hardware="zero-a10g",
space_secrets=[{"key": "HF_TOKEN", "value": hf_token}], # only if a gated/private repo is involved
exist_ok=True)(duplicate_space is the deprecated older name.) Confirm the source Space is currently RUNNING first (hf spaces info <id> --expand runtime) — a paused/broken source means bitrot. Duplication copies files only; pass space_hardware=/space_secrets= explicitly. Then hf download <repo> --repo-type space --local-dir ., make the minimal edits (checkpoint repo-id, title/README, UI trims), and push back with hf upload ... --repo-type space. Resist rewriting working extension-handling code you don't fully understand — sitecustomize patches, ctypes.CDLL preloads, autotune caches, and zero.startup() calls all look redundant until removed.
Build from source (custom UI, shape-only trims, no live official Space): start from the TripoSR skeleton (the ~200-line app documented in 3d-models.md — the cleanest template), vendor the model library tree from the official Space or GitHub, and follow 3d-cuda-extensions.md. Budget more debugging iterations than an image-model Space.
Either way, fetch the official Space's live files first (https://huggingface.co/spaces/{id}/raw/main/{path}) and treat them — not the reference files' pins — as source of truth. The reference files record what shipped as of mid-2026; the ZeroGPU runtime moves and official Spaces track it.
UI design
Read 3d-outputs.md for formats, viewers, preprocessing, and temp-file patterns. Baseline image-to-3D UI:
- Input: image upload → visible preprocessing preview (background removal + crop — show what the model actually receives) → generate.
- Controls: only the knobs this model responds to. Seed + randomize always; then per family: sampler steps/guidance (TRELLIS, Hunyuan3D), marching-cubes resolution (TripoSR), remesh/vertex-count/texture-size (SF3D), decimation + texture size at extraction (TRELLIS.2). Don't surface every config field of a research codebase.
- Output:
gr.Model3D(untextured meshes, splats) orLitModel3Dwith HDR lighting (textured/PBR), plusgr.DownloadButtons for GLB and secondary formats. Two-stage models (TRELLIS) show a fast turntable preview before the slower GLB extraction. - Examples: 3–6 known-good images lifted from the official Space's assets,
cache_examples=True, cache_mode="lazy".
Verify: 3D-specific additions
On top of the standard smoke test in SKILL.md §7:
These apps expose chained endpoints, not one
/predict— e.g. TripoSR:/preprocess(image → segmented image) then/generate(→ mesh files).Client(...).view_api()first, then call in sequence, feeding the first result into the second viahandle_file(...).Validate the returned file, not its existence. Meshes:
import trimesh m = trimesh.load("out.glb", force="scene") geoms = list(m.geometry.values()) if hasattr(m, "geometry") else [m] assert geoms and sum(g.faces.shape[0] for g in geoms) > 0, "empty mesh"Gaussian splat
.ply(no faces — check point count and splat attributes):from plyfile import PlyData v = PlyData.read("out.ply")["vertex"] assert v.count > 1000, "suspiciously few gaussians" assert {"f_dc_0", "opacity", "scale_0", "rot_0"} <= set(v.data.dtype.names), "not a gaussian ply"Look at it in the browser once. Orientation (upright? facing forward?), texture presence, viewer lighting — the failure modes a programmatic check can't catch (
3d-outputs.md→ Orientation).Startup-time extension compiles fail silently (app still boots, feature degrades) — grep the run log from the top even when everything looks green. Failure checklist: bottom of
3d-cuda-extensions.md.
What to avoid
- Assembling a TRELLIS/Hunyuan-class Space from scratch when a running official Space exists to duplicate.
- Trusting reference-file version pins over the live official Space's files.
- Compiling CUDA extensions via
requirements.txt(no nvcc at build time), orpip install-ing PyPI lookalike packages for the model libraries. - Returning meshes/gaussians as in-memory objects from
@spaces.GPUfunctions — write files, return paths. - Fixed output filenames (
output.glb) — concurrent users clobber each other. - Declaring green after
RUNNING+ a returned file. Load the mesh, count faces/points, and look at it once.