Spaces:
Configuration error
Configuration error
Upload 5 files
Browse files- README.md +20 -7
- app.py +37 -38
- pytorch3d/__init__.py +0 -0
- pytorch3d/transforms.py +24 -0
- requirements.txt +3 -3
README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
---
|
| 2 |
title: "WorldGen Text-to-3D"
|
| 3 |
-
emoji: "🌍"
|
| 4 |
colorFrom: "blue"
|
| 5 |
colorTo: "purple"
|
| 6 |
sdk: "gradio"
|
|
@@ -12,10 +11,24 @@ pinned: false
|
|
| 12 |
|
| 13 |
# WorldGen — Text -> 3D Scene (Hugging Face Spaces)
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
-
##
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
3. 下載 ZIP 檔(內含 3D 資產)。
|
|
|
|
| 1 |
---
|
| 2 |
title: "WorldGen Text-to-3D"
|
|
|
|
| 3 |
colorFrom: "blue"
|
| 4 |
colorTo: "purple"
|
| 5 |
sdk: "gradio"
|
|
|
|
| 11 |
|
| 12 |
# WorldGen — Text -> 3D Scene (Hugging Face Spaces)
|
| 13 |
|
| 14 |
+
This Space provides a minimal Text -> 3D demo using the open-source WorldGen project.
|
| 15 |
+
|
| 16 |
+
## Quick start
|
| 17 |
+
1) Open the Space and enter an English scene description.
|
| 18 |
+
2) Click "Generate". A ZIP file with 3D outputs will appear for download.
|
| 19 |
+
|
| 20 |
+
> Tip: On free CPU it will be very slow and may timeout. Switch the Space hardware to a GPU (T4/A10G) for practical generation.
|
| 21 |
+
|
| 22 |
+
## Environment
|
| 23 |
+
- Python 3.11 (set via the YAML block above)
|
| 24 |
+
- Gradio 5.x (set via sdk_version)
|
| 25 |
+
|
| 26 |
+
## Dependencies
|
| 27 |
+
We keep requirements minimal so pip can resolve versions:
|
| 28 |
+
- torch>=2.7 (required by WorldGen)
|
| 29 |
+
- websockets>=13.1 (required by viser used inside WorldGen)
|
| 30 |
+
- WorldGen from GitHub
|
| 31 |
|
| 32 |
+
## Notes
|
| 33 |
+
- This app disables CUDA and evaluation at runtime to avoid GPU-only/compiled ops when running on CPU.
|
| 34 |
+
- Temporary outputs are cleaned a few minutes after each run to avoid hitting the 50GB ephemeral disk limit.
|
|
|
app.py
CHANGED
|
@@ -1,30 +1,26 @@
|
|
| 1 |
-
# app.py —— 放在檔案最上方的環境設定(✦✦✦ 這段要在所有 import 之前 ✦✦✦)
|
| 2 |
import os
|
| 3 |
-
|
| 4 |
-
# 1) 禁用 CUDA(強制在 CPU 上跑,避免 CUDA 警告)
|
| 5 |
-
os.environ["CUDA_VISIBLE_DEVICES"] = ""
|
| 6 |
-
|
| 7 |
-
# 2) 關閉評估(若 WorldGen 支援此旗標,可避免 KNN/自訂外掛)
|
| 8 |
-
os.environ["WORLDGEN_DISABLE_EVAL"] = "1"
|
| 9 |
-
|
| 10 |
-
# 3) 指定 Gradio 暫存目錄(選用;集中暫存防止亂堆)
|
| 11 |
-
os.environ["GRADIO_TEMP_DIR"] = "/tmp/gradio"
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
# ===== 下面才開始 import 其他套件 =====
|
| 15 |
import shutil
|
| 16 |
import tempfile
|
| 17 |
import threading
|
| 18 |
from pathlib import Path
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
import gradio as gr
|
| 21 |
import torch
|
| 22 |
from worldgen import WorldGen
|
| 23 |
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
|
| 29 |
def _zip_directory(dir_path: Path) -> str:
|
| 30 |
zip_path = dir_path.with_suffix(".zip")
|
|
@@ -33,72 +29,75 @@ def _zip_directory(dir_path: Path) -> str:
|
|
| 33 |
shutil.make_archive(str(dir_path), "zip", root_dir=str(dir_path))
|
| 34 |
return str(zip_path)
|
| 35 |
|
| 36 |
-
|
| 37 |
def _save_scene(generated, out_dir: Path) -> Path:
|
| 38 |
out_dir.mkdir(parents=True, exist_ok=True)
|
| 39 |
|
| 40 |
-
#
|
| 41 |
for meth in ("save", "export", "write", "to_disk"):
|
| 42 |
if hasattr(generated, meth):
|
| 43 |
getattr(generated, meth)(str(out_dir))
|
| 44 |
return out_dir
|
| 45 |
|
|
|
|
| 46 |
for meth in ("save_as_glb", "to_glb", "save_as_ply", "to_ply"):
|
| 47 |
if hasattr(generated, meth):
|
| 48 |
fp = out_dir / ("scene.glb" if "glb" in meth else "scene.ply")
|
| 49 |
getattr(generated, meth)(str(fp))
|
| 50 |
return out_dir
|
| 51 |
|
| 52 |
-
#
|
| 53 |
(out_dir / "README.txt").write_text(
|
| 54 |
-
"
|
| 55 |
)
|
| 56 |
return out_dir
|
| 57 |
|
| 58 |
-
|
| 59 |
def generate_from_text(prompt: str):
|
| 60 |
if not prompt or not prompt.strip():
|
| 61 |
-
return None, "
|
|
|
|
|
|
|
| 62 |
|
| 63 |
try:
|
| 64 |
-
|
| 65 |
except Exception as e:
|
| 66 |
-
return None, f"
|
| 67 |
|
| 68 |
-
# 臨時目錄寫入與壓縮
|
| 69 |
tmpdir = Path(tempfile.mkdtemp())
|
| 70 |
out_dir = tmpdir / "worldgen_output"
|
| 71 |
-
out_dir = _save_scene(
|
| 72 |
zip_path = _zip_directory(out_dir)
|
| 73 |
|
| 74 |
-
#
|
| 75 |
def _cleanup(p: Path):
|
| 76 |
try:
|
| 77 |
shutil.rmtree(p, ignore_errors=True)
|
| 78 |
except Exception:
|
| 79 |
pass
|
| 80 |
|
| 81 |
-
threading.Timer(300, _cleanup, args=(tmpdir,)).start() # 5
|
| 82 |
|
| 83 |
-
return zip_path, "
|
| 84 |
|
| 85 |
-
|
| 86 |
-
# ===== Gradio 介面 =====
|
| 87 |
with gr.Blocks(title="WorldGen • Text -> 3D Scene") as demo:
|
| 88 |
-
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
with gr.Row():
|
| 91 |
with gr.Column():
|
| 92 |
prompt = gr.Textbox(
|
| 93 |
-
label="
|
| 94 |
placeholder="a neon-lit cyberpunk street with rain reflections, highly detailed",
|
| 95 |
lines=3,
|
| 96 |
)
|
| 97 |
-
|
| 98 |
with gr.Column():
|
| 99 |
-
out_zip = gr.File(label="
|
| 100 |
status = gr.Markdown()
|
| 101 |
|
| 102 |
-
|
| 103 |
|
| 104 |
-
demo.launch()
|
|
|
|
|
|
|
| 1 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import shutil
|
| 3 |
import tempfile
|
| 4 |
import threading
|
| 5 |
from pathlib import Path
|
| 6 |
|
| 7 |
+
# === Runtime environment tweaks (must be set before importing heavy libs) ===
|
| 8 |
+
os.environ.setdefault("CUDA_VISIBLE_DEVICES", "") # force CPU
|
| 9 |
+
os.environ.setdefault("WORLDGEN_DISABLE_EVAL", "1") # avoid KNN/eval paths if supported
|
| 10 |
+
os.environ.setdefault("GRADIO_TEMP_DIR", "/tmp/gradio") # centralize gradio cache
|
| 11 |
+
|
| 12 |
import gradio as gr
|
| 13 |
import torch
|
| 14 |
from worldgen import WorldGen
|
| 15 |
|
| 16 |
+
# Lazy init to avoid long cold start
|
| 17 |
+
_wg = None
|
| 18 |
|
| 19 |
+
def get_worldgen():
|
| 20 |
+
global _wg
|
| 21 |
+
if _wg is None:
|
| 22 |
+
_wg = WorldGen(mode="t2s", device="cpu", low_vram=True)
|
| 23 |
+
return _wg
|
| 24 |
|
| 25 |
def _zip_directory(dir_path: Path) -> str:
|
| 26 |
zip_path = dir_path.with_suffix(".zip")
|
|
|
|
| 29 |
shutil.make_archive(str(dir_path), "zip", root_dir=str(dir_path))
|
| 30 |
return str(zip_path)
|
| 31 |
|
|
|
|
| 32 |
def _save_scene(generated, out_dir: Path) -> Path:
|
| 33 |
out_dir.mkdir(parents=True, exist_ok=True)
|
| 34 |
|
| 35 |
+
# 1) try common object methods
|
| 36 |
for meth in ("save", "export", "write", "to_disk"):
|
| 37 |
if hasattr(generated, meth):
|
| 38 |
getattr(generated, meth)(str(out_dir))
|
| 39 |
return out_dir
|
| 40 |
|
| 41 |
+
# 2) single-file export fallbacks
|
| 42 |
for meth in ("save_as_glb", "to_glb", "save_as_ply", "to_ply"):
|
| 43 |
if hasattr(generated, meth):
|
| 44 |
fp = out_dir / ("scene.glb" if "glb" in meth else "scene.ply")
|
| 45 |
getattr(generated, meth)(str(fp))
|
| 46 |
return out_dir
|
| 47 |
|
| 48 |
+
# 3) last resort: leave a note
|
| 49 |
(out_dir / "README.txt").write_text(
|
| 50 |
+
"No known save/export method detected. Please adjust _save_scene according to your WorldGen version."
|
| 51 |
)
|
| 52 |
return out_dir
|
| 53 |
|
|
|
|
| 54 |
def generate_from_text(prompt: str):
|
| 55 |
if not prompt or not prompt.strip():
|
| 56 |
+
return None, "Please enter a scene description (English works best)."
|
| 57 |
+
|
| 58 |
+
wg = get_worldgen()
|
| 59 |
|
| 60 |
try:
|
| 61 |
+
result = wg.generate_world(prompt.strip())
|
| 62 |
except Exception as e:
|
| 63 |
+
return None, f"Generation failed: {e}"
|
| 64 |
|
|
|
|
| 65 |
tmpdir = Path(tempfile.mkdtemp())
|
| 66 |
out_dir = tmpdir / "worldgen_output"
|
| 67 |
+
out_dir = _save_scene(result, out_dir)
|
| 68 |
zip_path = _zip_directory(out_dir)
|
| 69 |
|
| 70 |
+
# delayed cleanup to avoid filling the 50GB ephemeral disk
|
| 71 |
def _cleanup(p: Path):
|
| 72 |
try:
|
| 73 |
shutil.rmtree(p, ignore_errors=True)
|
| 74 |
except Exception:
|
| 75 |
pass
|
| 76 |
|
| 77 |
+
threading.Timer(300, _cleanup, args=(tmpdir,)).start() # 5 minutes later
|
| 78 |
|
| 79 |
+
return zip_path, "Done. Download the ZIP (contains 3D assets)."
|
| 80 |
|
|
|
|
|
|
|
| 81 |
with gr.Blocks(title="WorldGen • Text -> 3D Scene") as demo:
|
| 82 |
+
gr.Markdown(
|
| 83 |
+
"""# WorldGen - Text -> 3D
|
| 84 |
+
- CPU-only fallback is enabled. For real runs, switch the Space to a GPU (T4/A10G).
|
| 85 |
+
- Outputs are zipped for download; temp files are cleaned shortly after each run.
|
| 86 |
+
|
| 87 |
+
"""
|
| 88 |
+
)
|
| 89 |
with gr.Row():
|
| 90 |
with gr.Column():
|
| 91 |
prompt = gr.Textbox(
|
| 92 |
+
label="Scene prompt (English)",
|
| 93 |
placeholder="a neon-lit cyberpunk street with rain reflections, highly detailed",
|
| 94 |
lines=3,
|
| 95 |
)
|
| 96 |
+
btn = gr.Button("Generate 3D Scene")
|
| 97 |
with gr.Column():
|
| 98 |
+
out_zip = gr.File(label="Download output (ZIP)")
|
| 99 |
status = gr.Markdown()
|
| 100 |
|
| 101 |
+
btn.click(generate_from_text, inputs=[prompt], outputs=[out_zip, status])
|
| 102 |
|
| 103 |
+
demo.launch()
|
pytorch3d/__init__.py
ADDED
|
File without changes
|
pytorch3d/transforms.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
def matrix_to_quaternion(R: torch.Tensor) -> torch.Tensor:
|
| 4 |
+
"""Minimal CPU-only implementation supporting [..., 3, 3] rotation matrices.
|
| 5 |
+
Returns quaternion in (x, y, z, w) order.
|
| 6 |
+
"""
|
| 7 |
+
R = R.float()
|
| 8 |
+
single = False
|
| 9 |
+
if R.dim() == 2:
|
| 10 |
+
R = R.unsqueeze(0)
|
| 11 |
+
single = True
|
| 12 |
+
m00 = R[..., 0, 0]; m01 = R[..., 0, 1]; m02 = R[..., 0, 2]
|
| 13 |
+
m10 = R[..., 1, 0]; m11 = R[..., 1, 1]; m12 = R[..., 1, 2]
|
| 14 |
+
m20 = R[..., 2, 0]; m21 = R[..., 2, 1]; m22 = R[..., 2, 2]
|
| 15 |
+
trace = m00 + m11 + m22
|
| 16 |
+
|
| 17 |
+
qw = torch.sqrt(torch.clamp(trace + 1.0, min=1e-8)) / 2.0
|
| 18 |
+
qx = torch.sign(m21 - m12) * torch.sqrt(torch.clamp(1.0 + m00 - m11 - m22, min=1e-8)) / 2.0
|
| 19 |
+
qy = torch.sign(m02 - m20) * torch.sqrt(torch.clamp(1.0 - m00 + m11 - m22, min=1e-8)) / 2.0
|
| 20 |
+
qz = torch.sign(m10 - m01) * torch.sqrt(torch.clamp(1.0 - m00 - m11 + m22, min=1e-8)) / 2.0
|
| 21 |
+
q = torch.stack([qx, qy, qz, qw], dim=-1)
|
| 22 |
+
if single:
|
| 23 |
+
q = q.squeeze(0)
|
| 24 |
+
return q
|
requirements.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
torch>=2.7
|
| 2 |
-
# torchvision>=0.22
|
| 3 |
-
websockets>=13.1
|
| 4 |
-
git+https://github.com/ZiYang-xie/WorldGen.git
|
|
|
|
| 1 |
torch>=2.7
|
| 2 |
+
# torchvision>=0.22 # uncomment if needed by your WorldGen setup
|
| 3 |
+
websockets>=13.1
|
| 4 |
+
git+https://github.com/ZiYang-xie/WorldGen.git
|