Spaces:
Runtime error
Runtime error
Sync deps: Dockerfile system deps + ffmpeg, single pip flow
Browse files- Dockerfile +2 -0
- README.md +14 -0
- app.py +44 -16
- scripts/mesh_generator.py +14 -1
Dockerfile
CHANGED
|
@@ -19,6 +19,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 19 |
|
| 20 |
COPY requirements.txt .
|
| 21 |
RUN pip install --no-cache-dir -r requirements.txt
|
|
|
|
|
|
|
| 22 |
# torchmcubes must be built after torch is installed (CMake needs TorchConfig.cmake)
|
| 23 |
RUN pip install --no-cache-dir --no-build-isolation "git+https://github.com/tatsy/torchmcubes.git"
|
| 24 |
|
|
|
|
| 19 |
|
| 20 |
COPY requirements.txt .
|
| 21 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 22 |
+
# torchmcubes: build deps for --no-build-isolation (scikit-build-core, pybind11, ninja)
|
| 23 |
+
RUN pip install --no-cache-dir scikit-build-core pybind11 ninja
|
| 24 |
# torchmcubes must be built after torch is installed (CMake needs TorchConfig.cmake)
|
| 25 |
RUN pip install --no-cache-dir --no-build-isolation "git+https://github.com/tatsy/torchmcubes.git"
|
| 26 |
|
README.md
CHANGED
|
@@ -34,6 +34,20 @@ Add a token so the Space can download the model:
|
|
| 34 |
|
| 35 |
After that, run Skybox or Mesh again; the model will download on first use.
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
---
|
| 38 |
|
| 39 |
Model: [evoneural/evoneuralIn3D](https://huggingface.co/evoneural/evoneuralIn3D)
|
|
|
|
| 34 |
|
| 35 |
After that, run Skybox or Mesh again; the model will download on first use.
|
| 36 |
|
| 37 |
+
## Ready to use
|
| 38 |
+
|
| 39 |
+
1. Push this repo to a **Docker** Space (SDK: docker, port 7860).
|
| 40 |
+
2. Set the **HF_TOKEN** secret (Settings → Variables and secrets).
|
| 41 |
+
3. Restart the Space. First build may take **15–25 minutes** (installing PyTorch, TripoSR, torchmcubes).
|
| 42 |
+
4. Open the app and generate a skybox or mesh. First run will download the Stable Diffusion model (~4 GB).
|
| 43 |
+
|
| 44 |
+
## Troubleshooting
|
| 45 |
+
|
| 46 |
+
- **"Could not load Stable Diffusion"** → Add `HF_TOKEN` in Settings → Variables and secrets, then restart the Space.
|
| 47 |
+
- **"TripoSR not found"** → Rebuild the Space (the Dockerfile clones TripoSR). If you forked the Space, ensure the Dockerfile and `scripts/` are present.
|
| 48 |
+
- **Mesh generation times out** → Use **Mesh resolution 256** and disable **Bake texture atlas** for a faster run; GPU Spaces are much faster than CPU.
|
| 49 |
+
- **Build fails on torchmcubes** → The Dockerfile installs torch first, then builds torchmcubes with `--no-build-isolation` so CMake finds Torch. Do not change the order of `pip install` steps.
|
| 50 |
+
|
| 51 |
---
|
| 52 |
|
| 53 |
Model: [evoneural/evoneuralIn3D](https://huggingface.co/evoneural/evoneuralIn3D)
|
app.py
CHANGED
|
@@ -18,6 +18,9 @@ import streamlit as st
|
|
| 18 |
OUTPUTS = ROOT / "outputs"
|
| 19 |
OUTPUTS.mkdir(exist_ok=True)
|
| 20 |
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
def main() -> None:
|
| 23 |
st.set_page_config(
|
|
@@ -25,29 +28,41 @@ def main() -> None:
|
|
| 25 |
page_icon="🎮",
|
| 26 |
layout="wide",
|
| 27 |
)
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
# Sidebar: model setup (token + download)
|
| 32 |
with st.sidebar:
|
| 33 |
st.subheader("Stable Diffusion model")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
from scripts.skybox_generator import _default_local_weights_dir
|
| 35 |
local_model = _default_local_weights_dir()
|
| 36 |
if local_model:
|
| 37 |
-
st.success(
|
| 38 |
st.caption(os.path.basename(local_model))
|
| 39 |
-
|
| 40 |
st.warning("No local model. Download below or need internet on first generate.")
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
| 51 |
with st.spinner("Downloading model... (may take several minutes)"):
|
| 52 |
try:
|
| 53 |
from scripts.download_sd_model import download_sd_model
|
|
@@ -57,6 +72,13 @@ def main() -> None:
|
|
| 57 |
except Exception as e:
|
| 58 |
st.error(str(e))
|
| 59 |
st.caption("Set a Hugging Face token above if your network blocks Hugging Face.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
tab_mesh, tab_skybox = st.tabs(["🟦 Text → 3D Mesh", "🌅 Text → Skybox"])
|
| 62 |
|
|
@@ -130,8 +152,8 @@ def main() -> None:
|
|
| 130 |
triposr_root = find_triposr_root(str(ROOT))
|
| 131 |
if not triposr_root:
|
| 132 |
st.error(
|
| 133 |
-
"TripoSR not found.
|
| 134 |
-
"
|
| 135 |
)
|
| 136 |
elif image_path_to_use:
|
| 137 |
path = image_path_to_use
|
|
@@ -140,6 +162,8 @@ def main() -> None:
|
|
| 140 |
with open(path, "wb") as f:
|
| 141 |
f.write(uploaded.getvalue())
|
| 142 |
if path != "upload" and os.path.isfile(path):
|
|
|
|
|
|
|
| 143 |
mesh_path, elapsed, msg = generate_mesh_from_image(
|
| 144 |
path,
|
| 145 |
output_dir=str(OUTPUTS / "mesh_run"),
|
|
@@ -147,6 +171,7 @@ def main() -> None:
|
|
| 147 |
mc_resolution=mc_resolution,
|
| 148 |
bake_texture=bake_texture,
|
| 149 |
smooth_mesh=smooth_mesh,
|
|
|
|
| 150 |
)
|
| 151 |
if mesh_path:
|
| 152 |
st.success(f"Done in {elapsed:.1f}s. {msg}")
|
|
@@ -157,6 +182,8 @@ def main() -> None:
|
|
| 157 |
elif path == "upload":
|
| 158 |
st.warning("Upload an image first.")
|
| 159 |
else:
|
|
|
|
|
|
|
| 160 |
mesh_path, elapsed, msg = generate_mesh_from_text(
|
| 161 |
prompt_mesh,
|
| 162 |
output_dir=str(OUTPUTS),
|
|
@@ -165,6 +192,7 @@ def main() -> None:
|
|
| 165 |
mc_resolution=mc_resolution,
|
| 166 |
bake_texture=bake_texture,
|
| 167 |
smooth_mesh=smooth_mesh,
|
|
|
|
| 168 |
)
|
| 169 |
if mesh_path:
|
| 170 |
st.success(f"Done in {elapsed:.1f}s. {msg}")
|
|
|
|
| 18 |
OUTPUTS = ROOT / "outputs"
|
| 19 |
OUTPUTS.mkdir(exist_ok=True)
|
| 20 |
|
| 21 |
+
# Hugging Face Space: HF sets SPACE_ID when running in a Space
|
| 22 |
+
IS_HF_SPACE = bool(os.environ.get("SPACE_ID") or os.environ.get("SPACE_REPO_ID"))
|
| 23 |
+
|
| 24 |
|
| 25 |
def main() -> None:
|
| 26 |
st.set_page_config(
|
|
|
|
| 28 |
page_icon="🎮",
|
| 29 |
layout="wide",
|
| 30 |
)
|
| 31 |
+
if IS_HF_SPACE:
|
| 32 |
+
st.title("EvoneuralIn3D – Mesh & Skybox")
|
| 33 |
+
st.caption("Text → 3D mesh (TripoSR) and Text → 360° skybox (Stable Diffusion). Running on Hugging Face Space.")
|
| 34 |
+
else:
|
| 35 |
+
st.title("Evoneural MVP – Local Mesh & Skybox")
|
| 36 |
+
st.caption("Text → 3D mesh (TripoSR) and Text → 360° skybox (Stable Diffusion). Runs on localhost.")
|
| 37 |
|
| 38 |
# Sidebar: model setup (token + download)
|
| 39 |
with st.sidebar:
|
| 40 |
st.subheader("Stable Diffusion model")
|
| 41 |
+
hf_token_env = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN")
|
| 42 |
+
if IS_HF_SPACE:
|
| 43 |
+
if hf_token_env:
|
| 44 |
+
st.success("HF_TOKEN is set (from Space secrets)")
|
| 45 |
+
else:
|
| 46 |
+
st.error("HF_TOKEN not set")
|
| 47 |
+
st.caption("Add it in this Space: **Settings** → **Variables and secrets** → New secret: `HF_TOKEN`. Then restart the Space.")
|
| 48 |
from scripts.skybox_generator import _default_local_weights_dir
|
| 49 |
local_model = _default_local_weights_dir()
|
| 50 |
if local_model:
|
| 51 |
+
st.success("Local model: found")
|
| 52 |
st.caption(os.path.basename(local_model))
|
| 53 |
+
elif not IS_HF_SPACE:
|
| 54 |
st.warning("No local model. Download below or need internet on first generate.")
|
| 55 |
+
if not IS_HF_SPACE:
|
| 56 |
+
hf_token = st.text_input(
|
| 57 |
+
"Hugging Face token (optional, if behind firewall)",
|
| 58 |
+
type="password",
|
| 59 |
+
key="hf_token",
|
| 60 |
+
placeholder="hf_...",
|
| 61 |
+
help="Get a token at huggingface.co/settings/tokens",
|
| 62 |
+
)
|
| 63 |
+
if hf_token:
|
| 64 |
+
os.environ["HF_TOKEN"] = hf_token
|
| 65 |
+
if not IS_HF_SPACE and st.button("Download model (~4GB to ./weights/sd-v1-5)", key="btn_download"):
|
| 66 |
with st.spinner("Downloading model... (may take several minutes)"):
|
| 67 |
try:
|
| 68 |
from scripts.download_sd_model import download_sd_model
|
|
|
|
| 72 |
except Exception as e:
|
| 73 |
st.error(str(e))
|
| 74 |
st.caption("Set a Hugging Face token above if your network blocks Hugging Face.")
|
| 75 |
+
# Environment check: TripoSR (useful in Space)
|
| 76 |
+
from scripts.mesh_generator import find_triposr_root as _find_triposr
|
| 77 |
+
triposr_ok = _find_triposr(str(ROOT)) is not None
|
| 78 |
+
if triposr_ok:
|
| 79 |
+
st.caption("TripoSR: ready")
|
| 80 |
+
else:
|
| 81 |
+
st.caption("TripoSR: not found (mesh tab will show instructions)")
|
| 82 |
|
| 83 |
tab_mesh, tab_skybox = st.tabs(["🟦 Text → 3D Mesh", "🌅 Text → Skybox"])
|
| 84 |
|
|
|
|
| 152 |
triposr_root = find_triposr_root(str(ROOT))
|
| 153 |
if not triposr_root:
|
| 154 |
st.error(
|
| 155 |
+
"TripoSR not found. In this Space the Docker image should include it. "
|
| 156 |
+
"If you see this, rebuild the Space or check the Dockerfile."
|
| 157 |
)
|
| 158 |
elif image_path_to_use:
|
| 159 |
path = image_path_to_use
|
|
|
|
| 162 |
with open(path, "wb") as f:
|
| 163 |
f.write(uploaded.getvalue())
|
| 164 |
if path != "upload" and os.path.isfile(path):
|
| 165 |
+
import torch as _torch
|
| 166 |
+
_dev = "cuda:0" if _torch.cuda.is_available() else "cpu"
|
| 167 |
mesh_path, elapsed, msg = generate_mesh_from_image(
|
| 168 |
path,
|
| 169 |
output_dir=str(OUTPUTS / "mesh_run"),
|
|
|
|
| 171 |
mc_resolution=mc_resolution,
|
| 172 |
bake_texture=bake_texture,
|
| 173 |
smooth_mesh=smooth_mesh,
|
| 174 |
+
device=_dev,
|
| 175 |
)
|
| 176 |
if mesh_path:
|
| 177 |
st.success(f"Done in {elapsed:.1f}s. {msg}")
|
|
|
|
| 182 |
elif path == "upload":
|
| 183 |
st.warning("Upload an image first.")
|
| 184 |
else:
|
| 185 |
+
import torch as _torch
|
| 186 |
+
_dev = "cuda:0" if _torch.cuda.is_available() else "cpu"
|
| 187 |
mesh_path, elapsed, msg = generate_mesh_from_text(
|
| 188 |
prompt_mesh,
|
| 189 |
output_dir=str(OUTPUTS),
|
|
|
|
| 192 |
mc_resolution=mc_resolution,
|
| 193 |
bake_texture=bake_texture,
|
| 194 |
smooth_mesh=smooth_mesh,
|
| 195 |
+
device=_dev,
|
| 196 |
)
|
| 197 |
if mesh_path:
|
| 198 |
st.success(f"Done in {elapsed:.1f}s. {msg}")
|
scripts/mesh_generator.py
CHANGED
|
@@ -82,7 +82,7 @@ def generate_mesh_from_image(
|
|
| 82 |
output_dir: str = "outputs",
|
| 83 |
mesh_format: str = "glb",
|
| 84 |
triposr_root: str | None = None,
|
| 85 |
-
device: str =
|
| 86 |
use_hunyuan3d2: bool | None = None,
|
| 87 |
mc_resolution: int = 512,
|
| 88 |
bake_texture: bool = True,
|
|
@@ -118,6 +118,7 @@ def generate_mesh_from_image(
|
|
| 118 |
"TripoSR not found. Clone it: git clone https://github.com/VAST-AI-Research/TripoSR.git",
|
| 119 |
)
|
| 120 |
|
|
|
|
| 121 |
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
| 122 |
# When baking texture, xatlas.export() always writes OBJ format (ignores .glb extension).
|
| 123 |
# Ask for OBJ when bake_texture + glb, then we convert OBJ+texture to real GLB.
|
|
@@ -204,6 +205,15 @@ def find_hunyuan3d2_root(project_root: str | None = None) -> str | None:
|
|
| 204 |
return None
|
| 205 |
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
def generate_mesh_from_text(
|
| 208 |
prompt: str,
|
| 209 |
output_dir: str = "outputs",
|
|
@@ -214,6 +224,7 @@ def generate_mesh_from_text(
|
|
| 214 |
bake_texture: bool = True,
|
| 215 |
texture_resolution: int = 2048,
|
| 216 |
smooth_mesh: bool = True,
|
|
|
|
| 217 |
) -> tuple[str | None, float, str]:
|
| 218 |
"""
|
| 219 |
Text → 3D mesh. Uses Hunyuan3D-2 full pipeline when available (use_hunyuan3d2=True or repo found),
|
|
@@ -248,6 +259,7 @@ def generate_mesh_from_text(
|
|
| 248 |
image_path, _ = text_to_image(prompt, output_dir=output_dir, seed=seed)
|
| 249 |
except Exception as e:
|
| 250 |
return (None, 0.0, f"Text-to-image failed: {e}")
|
|
|
|
| 251 |
mesh_path, mesh_time, msg = generate_mesh_from_image(
|
| 252 |
image_path,
|
| 253 |
output_dir=os.path.join(output_dir, "mesh_run"),
|
|
@@ -256,6 +268,7 @@ def generate_mesh_from_text(
|
|
| 256 |
bake_texture=bake_texture,
|
| 257 |
texture_resolution=texture_resolution,
|
| 258 |
smooth_mesh=smooth_mesh,
|
|
|
|
| 259 |
)
|
| 260 |
total_time = time.perf_counter() - t0
|
| 261 |
if mesh_path:
|
|
|
|
| 82 |
output_dir: str = "outputs",
|
| 83 |
mesh_format: str = "glb",
|
| 84 |
triposr_root: str | None = None,
|
| 85 |
+
device: str | None = None,
|
| 86 |
use_hunyuan3d2: bool | None = None,
|
| 87 |
mc_resolution: int = 512,
|
| 88 |
bake_texture: bool = True,
|
|
|
|
| 118 |
"TripoSR not found. Clone it: git clone https://github.com/VAST-AI-Research/TripoSR.git",
|
| 119 |
)
|
| 120 |
|
| 121 |
+
device = device or _infer_device()
|
| 122 |
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
| 123 |
# When baking texture, xatlas.export() always writes OBJ format (ignores .glb extension).
|
| 124 |
# Ask for OBJ when bake_texture + glb, then we convert OBJ+texture to real GLB.
|
|
|
|
| 205 |
return None
|
| 206 |
|
| 207 |
|
| 208 |
+
def _infer_device() -> str:
|
| 209 |
+
"""Use GPU if available, else CPU (for CPU-only Spaces)."""
|
| 210 |
+
try:
|
| 211 |
+
import torch
|
| 212 |
+
return "cuda:0" if torch.cuda.is_available() else "cpu"
|
| 213 |
+
except Exception:
|
| 214 |
+
return "cpu"
|
| 215 |
+
|
| 216 |
+
|
| 217 |
def generate_mesh_from_text(
|
| 218 |
prompt: str,
|
| 219 |
output_dir: str = "outputs",
|
|
|
|
| 224 |
bake_texture: bool = True,
|
| 225 |
texture_resolution: int = 2048,
|
| 226 |
smooth_mesh: bool = True,
|
| 227 |
+
device: str | None = None,
|
| 228 |
) -> tuple[str | None, float, str]:
|
| 229 |
"""
|
| 230 |
Text → 3D mesh. Uses Hunyuan3D-2 full pipeline when available (use_hunyuan3d2=True or repo found),
|
|
|
|
| 259 |
image_path, _ = text_to_image(prompt, output_dir=output_dir, seed=seed)
|
| 260 |
except Exception as e:
|
| 261 |
return (None, 0.0, f"Text-to-image failed: {e}")
|
| 262 |
+
device = device or _infer_device()
|
| 263 |
mesh_path, mesh_time, msg = generate_mesh_from_image(
|
| 264 |
image_path,
|
| 265 |
output_dir=os.path.join(output_dir, "mesh_run"),
|
|
|
|
| 268 |
bake_texture=bake_texture,
|
| 269 |
texture_resolution=texture_resolution,
|
| 270 |
smooth_mesh=smooth_mesh,
|
| 271 |
+
device=device,
|
| 272 |
)
|
| 273 |
total_time = time.perf_counter() - t0
|
| 274 |
if mesh_path:
|