Skip to content

Commit d2e56a8

Browse files
committed
fix failing bundle missing file
1 parent 11cb591 commit d2e56a8

6 files changed

Lines changed: 73 additions & 37 deletions

File tree

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,15 @@ dev = [
157157
"ruff>=0.9.0",
158158
"twine>=6.0.0",
159159
]
160+
torchmcubes = [
161+
"torchmcubes",
162+
]
163+
164+
[tool.uv.sources]
165+
torchmcubes = { git = "https://github.com/tatsy/torchmcubes.git" }
166+
167+
[tool.uv.extra-build-dependencies]
168+
torchmcubes = ["torch"]
160169

161170
# ---------------------------------------------------------------------------
162171
# Hatch build — wheel & sdist

tests/integration/test_reconstruction_direct.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from __future__ import annotations
1010

11+
import importlib.util
1112
import json
1213
import os
1314
import time
@@ -18,12 +19,8 @@
1819
import pytest
1920
from PIL import Image
2021

21-
pytest.importorskip("open3d", reason="open3d required")
22-
pytest.importorskip("trimesh", reason="trimesh required")
23-
pytest.importorskip("rembg", reason="rembg required")
24-
pytest.importorskip("mcubes", reason="PyMCubes required")
25-
pytest.importorskip("omegaconf", reason="omegaconf required")
26-
pytest.importorskip("einops", reason="einops required")
22+
for module_name in ("open3d", "trimesh", "rembg", "mcubes", "omegaconf", "einops"):
23+
assert importlib.util.find_spec(module_name), f"{module_name} is required"
2724

2825
from vizion3d.lifting.utils import create_ply_binary # noqa: E402
2926
from vizion3d.reconstruction import ( # noqa: E402
@@ -34,6 +31,7 @@
3431
SceneComponents3DReconstructionCommand,
3532
SceneComponents3DReconstructionConfig,
3633
)
34+
from vizion3d.reconstruction.defaults import resolve_model_bundle # noqa: E402
3735
from vizion3d.reconstruction.handlers import Object3DReconstructionHandler # noqa: E402
3836

3937
OBJECT_LIMIT = float(os.environ.get("VIZION3D_TEST_RECON_OBJECT_LIMIT", "240.0"))
@@ -44,17 +42,13 @@
4442

4543
@pytest.fixture(scope="session")
4644
def reconstruction_model_bundle() -> str:
47-
bundle = Path(__file__).resolve().parents[2] / "scene-components-3d-models.zip"
48-
if not bundle.is_file():
49-
pytest.skip(f"Reconstruction model bundle not found: {bundle}")
50-
return str(bundle)
45+
return str(resolve_model_bundle())
5146

5247

5348
@pytest.fixture(scope="session")
5449
def reconstruction_image_bytes() -> bytes:
5550
path = Path(__file__).parent.parent / "assets" / RECONSTRUCTION_IMAGE
56-
if not path.is_file():
57-
pytest.skip(f"Reconstruction integration image not found: {path}")
51+
assert path.is_file(), f"Reconstruction integration image not found: {path}"
5852
image = Image.open(path)
5953
assert max(image.size) <= TEST_IMAGE_MAX_DIMENSION
6054
return path.read_bytes()

tests/integration/test_reconstruction_grpc.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from __future__ import annotations
99

10+
import importlib.util
1011
import json
1112
import os
1213
import time
@@ -15,14 +16,11 @@
1516
import pytest
1617
from PIL import Image
1718

18-
pytest.importorskip("open3d", reason="open3d required")
19-
pytest.importorskip("trimesh", reason="trimesh required")
20-
pytest.importorskip("rembg", reason="rembg required")
21-
pytest.importorskip("mcubes", reason="PyMCubes required")
22-
pytest.importorskip("omegaconf", reason="omegaconf required")
23-
pytest.importorskip("einops", reason="einops required")
19+
for module_name in ("open3d", "trimesh", "rembg", "mcubes", "omegaconf", "einops"):
20+
assert importlib.util.find_spec(module_name), f"{module_name} is required"
2421

2522
from vizion3d.proto import lifting_pb2 # noqa: E402
23+
from vizion3d.reconstruction.defaults import resolve_model_bundle # noqa: E402
2624
from vizion3d.reconstruction.handlers import Object3DReconstructionHandler # noqa: E402
2725

2826
OBJECT_LIMIT = float(os.environ.get("VIZION3D_TEST_RECON_GRPC_OBJECT_LIMIT", "260.0"))
@@ -33,17 +31,13 @@
3331

3432
@pytest.fixture(scope="session")
3533
def reconstruction_model_bundle() -> str:
36-
bundle = Path(__file__).resolve().parents[2] / "scene-components-3d-models.zip"
37-
if not bundle.is_file():
38-
pytest.skip(f"Reconstruction model bundle not found: {bundle}")
39-
return str(bundle)
34+
return str(resolve_model_bundle())
4035

4136

4237
@pytest.fixture(scope="session")
4338
def reconstruction_image_bytes() -> bytes:
4439
path = Path(__file__).parent.parent / "assets" / RECONSTRUCTION_IMAGE
45-
if not path.is_file():
46-
pytest.skip(f"Reconstruction integration image not found: {path}")
40+
assert path.is_file(), f"Reconstruction integration image not found: {path}"
4741
image = Image.open(path)
4842
assert max(image.size) <= TEST_IMAGE_MAX_DIMENSION
4943
return path.read_bytes()

tests/integration/test_reconstruction_rest.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from __future__ import annotations
99

1010
import base64
11+
import importlib.util
1112
import json
1213
import os
1314
import time
@@ -17,13 +18,10 @@
1718
from fastapi.testclient import TestClient
1819
from PIL import Image
1920

20-
pytest.importorskip("open3d", reason="open3d required")
21-
pytest.importorskip("trimesh", reason="trimesh required")
22-
pytest.importorskip("rembg", reason="rembg required")
23-
pytest.importorskip("mcubes", reason="PyMCubes required")
24-
pytest.importorskip("omegaconf", reason="omegaconf required")
25-
pytest.importorskip("einops", reason="einops required")
21+
for module_name in ("open3d", "trimesh", "rembg", "mcubes", "omegaconf", "einops"):
22+
assert importlib.util.find_spec(module_name), f"{module_name} is required"
2623

24+
from vizion3d.reconstruction.defaults import resolve_model_bundle # noqa: E402
2725
from vizion3d.reconstruction.handlers import Object3DReconstructionHandler # noqa: E402
2826
from vizion3d.server.rest.app import app # noqa: E402
2927

@@ -37,17 +35,13 @@
3735

3836
@pytest.fixture(scope="session")
3937
def reconstruction_model_bundle() -> str:
40-
bundle = Path(__file__).resolve().parents[2] / "scene-components-3d-models.zip"
41-
if not bundle.is_file():
42-
pytest.skip(f"Reconstruction model bundle not found: {bundle}")
43-
return str(bundle)
38+
return str(resolve_model_bundle())
4439

4540

4641
@pytest.fixture(scope="session")
4742
def reconstruction_image_bytes() -> bytes:
4843
path = Path(__file__).parent.parent / "assets" / RECONSTRUCTION_IMAGE
49-
if not path.is_file():
50-
pytest.skip(f"Reconstruction integration image not found: {path}")
44+
assert path.is_file(), f"Reconstruction integration image not found: {path}"
5145
image = Image.open(path)
5246
assert max(image.size) <= TEST_IMAGE_MAX_DIMENSION
5347
return path.read_bytes()

uv.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vizion3d/reconstruction/handlers.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import os
99
import sys
1010
import threading
11+
import types
1112
from pathlib import Path
1213
from types import ModuleType
1314

@@ -100,6 +101,35 @@ def _module_file(module: ModuleType | None) -> Path | None:
100101
return Path(module.__file__).resolve()
101102

102103

104+
def _ensure_torchmcubes_compat() -> None:
105+
try:
106+
import torchmcubes # noqa: F401
107+
108+
return
109+
except ImportError:
110+
pass
111+
112+
try:
113+
import mcubes
114+
import torch
115+
except ImportError as exc:
116+
raise ImportError(
117+
"3D reconstruction requires torchmcubes or PyMCubes for marching cubes."
118+
) from exc
119+
120+
module = types.ModuleType("torchmcubes")
121+
122+
def marching_cubes(volume, threshold):
123+
vertices, faces = mcubes.marching_cubes(volume.detach().cpu().numpy(), threshold)
124+
return (
125+
torch.as_tensor(vertices, dtype=volume.dtype, device=volume.device),
126+
torch.as_tensor(faces, dtype=torch.long, device=volume.device),
127+
)
128+
129+
module.marching_cubes = marching_cubes
130+
sys.modules["torchmcubes"] = module
131+
132+
103133
def _import_tsr_from_bundle(root: Path):
104134
source = (root / "TripoSR").resolve()
105135
system_py = source / "tsr" / "system.py"
@@ -117,6 +147,7 @@ def _import_tsr_from_bundle(root: Path):
117147
if source_str in sys.path:
118148
sys.path.remove(source_str)
119149
sys.path.insert(0, source_str)
150+
_ensure_torchmcubes_compat()
120151

121152
try:
122153
from tsr.system import TSR
@@ -172,7 +203,8 @@ def _load_model(self, model_bundle: str | None, requested_device: str):
172203
import trimesh
173204
except ImportError as exc:
174205
raise ImportError(
175-
"3D reconstruction requires torch, trimesh, einops, omegaconf, and PyMCubes."
206+
"3D reconstruction requires torch, trimesh, einops, omegaconf, "
207+
"and PyMCubes or torchmcubes."
176208
) from exc
177209

178210
triposr = root / "TripoSR"

0 commit comments

Comments
 (0)