Bump version to 0.11.2 and update uv version to 0.11.3

- Updated version in pyproject.toml and blender_manifest.toml to 0.11.2.
- Enhanced uv executable path documentation in client.py.
- Added inference_mode parameter to reconstruct_request function in client.py.
- Implemented uv download functionality in install_env.py with improved error handling and checksum verification.
- Modified install_uv function to download and extract uv binary instead of using bundled version.
- Updated job progress handling in operators.py to track progress stages.
- Improved download progress reporting in worker.py and adjusted related tests.
- Refactored tests to accommodate changes in status reporting and model downloading.
- Removed vendor.lock.toml as part of the cleanup process.
This commit is contained in:
2026-08-14 23:43:01 +08:00
parent 102e1bfa9b
commit 0c8d3a8988
22 changed files with 497 additions and 511 deletions

View File

@@ -849,7 +849,7 @@ class AddonTest(unittest.TestCase):
},
"fallback",
),
"3/8 pytorch | 2 downloaded",
"3/8 2 downloaded",
)
self.assertEqual(
operator._format_status(
@@ -886,7 +886,7 @@ class AddonTest(unittest.TestCase):
},
"fallback",
),
"2/3 PyTorch | Downloading packages | About 3 min left",
"2/3 Downloading packages",
)
self.assertEqual(
operator._format_status(
@@ -902,6 +902,30 @@ class AddonTest(unittest.TestCase):
"2/3 PyTorch",
)
def test_update_status_resets_progress_when_stage_changes(self):
operator = self.addon.RunJob()
settings = SimpleNamespace(progress=0.9, status="previous")
context = SimpleNamespace(
scene=SimpleNamespace(snapform_settings=settings)
)
job = SimpleNamespace(status_path=Path("status.json"), progress_stage=1)
with patch.object(
self.addon.client,
"read_status",
return_value={
"progress": 0.1,
"message": "Downloading model",
"stage": 2,
"stages": 4,
"stage_label": "MoGe-2",
},
), patch.object(operator, "_redraw_ui"):
operator._update_status(context, job)
self.assertEqual(job.progress_stage, 2)
self.assertEqual(settings.progress, 0.1)
def test_model_options_mark_only_models_that_are_not_downloaded(self):
items_callback = self.addon.SnapFormSettings.__annotations__["model"][
"items"
@@ -1419,6 +1443,7 @@ class AddonTest(unittest.TestCase):
parameters = request.payload["parameters"]
self.assertEqual(request.payload["command"], "reconstruct")
self.assertEqual(parameters["inference_mode"], "joint")
self.assertEqual(parameters["export_format"], "gs_ply")
for parameter in (
"process_res",
@@ -1660,6 +1685,10 @@ class AddonTest(unittest.TestCase):
self.fake_bpy.data.objects.get = original_get
self.assertEqual(reconstruct.call_args.kwargs["start_frame"], 71)
self.assertEqual(
reconstruct.call_args.kwargs["inference_mode"],
"per_frame",
)
def test_reconstruct_request_accepts_image_depth_overrides(self):
client = importlib.import_module("snapform.client")

View File

@@ -110,10 +110,10 @@ class DownloadProgressTest(unittest.TestCase):
status = json.loads(status_path.read_text(encoding="utf-8"))
self.assertEqual(
status["message"],
"Downloading da3 small | 13.4 MB/s",
"Downloading da3 small 13.4 MB/s",
)
def test_reports_estimated_time_remaining(self):
def test_does_not_report_estimated_time_remaining(self):
times = iter((0.0, 10.0))
with tempfile.TemporaryDirectory() as directory:
status_path = Path(directory) / "status.json"
@@ -133,9 +133,9 @@ class DownloadProgressTest(unittest.TestCase):
progress.update(50)
status = json.loads(status_path.read_text(encoding="utf-8"))
self.assertEqual(status["eta_seconds"], 10)
self.assertNotIn("eta_seconds", status)
def test_staged_download_prefixes_speed_with_downloading(self):
def test_staged_download_keeps_model_label(self):
class FakeTqdmWithRate(FakeTqdm):
@property
def format_dict(self):
@@ -165,7 +165,7 @@ class DownloadProgressTest(unittest.TestCase):
status = json.loads(status_path.read_text(encoding="utf-8"))
self.assertEqual(
status["message"],
"Downloading packages | 13.4 MB/s",
"Downloading da3 small 13.4 MB/s",
)
self.assertEqual(status["stage"], 6)
self.assertEqual(status["stages"], 8)

View File

@@ -1,7 +1,9 @@
import importlib.util
import io
import hashlib
import json
import os
import shutil
import subprocess
import tarfile
import tempfile
@@ -129,8 +131,8 @@ class InstallEnvTest(unittest.TestCase):
if value == "--allow-pattern"
]
self.assertEqual(patterns, ["onnx/model_fp16.onnx"])
self.assertNotIn("--stage", ben2_download)
self.assertNotIn("--stage-label", ben2_download)
self.assertIn("--stage", ben2_download)
self.assertIn("--stage-label", ben2_download)
self.assertEqual(
ben2_download[ben2_download.index("--label") + 1],
"BEN2 Base",
@@ -305,7 +307,7 @@ class InstallEnvTest(unittest.TestCase):
)
self.assertEqual(called_command[-1], "da3 small")
def test_install_allocates_contiguous_progress_slices(self):
def test_install_resets_progress_for_each_stage(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
@@ -358,21 +360,8 @@ class InstallEnvTest(unittest.TestCase):
):
bootstrap.install(args)
starts = [item[1] for item in stage_ranges]
ends = [item[2] for item in stage_ranges]
self.assertTrue(
all(starts[i] < starts[i + 1] for i in range(len(starts) - 1))
)
self.assertEqual(ends[-1], 1.0)
self.assertEqual(starts[0], bootstrap.ENVIRONMENT_SLICE[0])
runtime_ranges = stage_ranges[:2]
self.assertEqual(
[item[0] for item in runtime_ranges],
[1, 2],
)
self.assertTrue(
all(item[0] is None for item in stage_ranges[4:])
)
self.assertEqual(stage_ranges[:2], [(1, 0.0, 0.05), (1, 0.05, 1.0)])
self.assertEqual(stage_ranges[2:], [(2, 0.0, 1.0), (3, 0.0, 1.0)])
status = json.loads(
(root / "status.json").read_text(encoding="utf-8")
)
@@ -884,13 +873,12 @@ class InstallEnvTest(unittest.TestCase):
)
)
def test_install_fails_when_bundled_uv_missing(self):
def test_install_fails_when_all_uv_sources_are_unavailable(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
venv_dir = root / ".venv"
venv_dir.mkdir()
self.create_bundled_vendor(root)
uv_path = root / "uv" / "uv.exe"
args = SimpleNamespace(
uv=str(uv_path),
@@ -916,29 +904,23 @@ class InstallEnvTest(unittest.TestCase):
"rmtree",
), patch.object(
bootstrap,
"bundled_uv_archive",
return_value=None,
"download_uv_archive",
side_effect=RuntimeError("all sources unavailable"),
):
with self.assertRaisesRegex(
RuntimeError,
"Bundled uv is missing",
"all sources unavailable",
):
bootstrap.install(args)
def test_install_uses_bundled_vendor_and_uv_without_network(self):
def test_install_downloads_and_extracts_uv(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
venv_dir = root / ".venv"
venv_dir.mkdir()
bundled = root / "bundled"
bundled.mkdir()
(bundled / "depth-anything-3.tar.gz").write_bytes(b"da3")
(bundled / "ben2.tar.gz").write_bytes(b"ben2")
(bundled / "utils3d.tar.gz").write_bytes(b"utils3d")
(bundled / "moge.tar.gz").write_bytes(b"moge")
target, archive_name = bootstrap.uv_download_target()
archive = bundled / f"uv-{target}.{archive_name}"
archive = root / f"uv-{target}.{archive_name}"
if archive_name.endswith("zip"):
with zipfile.ZipFile(archive, "w") as bundle:
bundle.writestr("uv/uv.exe", b"uv-binary")
@@ -978,14 +960,18 @@ class InstallEnvTest(unittest.TestCase):
install_commands.append(command)
return []
def fake_download(destination, *_args):
shutil.copy2(archive, destination)
return destination
with patch.object(
bootstrap,
"bundled_vendor_directory",
return_value=bundled,
), patch.object(
bootstrap,
"resolve_indexes",
return_value=None,
), patch.object(
bootstrap,
"download_uv_archive",
side_effect=fake_download,
), patch.object(
bootstrap,
"run",
@@ -1003,13 +989,6 @@ class InstallEnvTest(unittest.TestCase):
self.assertEqual(uv_path.read_bytes(), b"uv-binary")
all_commands = run_commands + install_commands
self.assertFalse(
any(
"github.com" in str(value)
for command in all_commands
for value in command
)
)
runtime_command = next(
command
for command in install_commands
@@ -1017,6 +996,9 @@ class InstallEnvTest(unittest.TestCase):
)
self.assertFalse(any(" @ file:" in str(value) for value in runtime_command))
self.assertFalse(any("torch" in str(value) for value in runtime_command))
status = json.loads(Path(args.status).read_text(encoding="utf-8"))
self.assertEqual(status["stage"], 4)
self.assertEqual(status["stages"], 4)
def test_install_reuses_existing_uv(self):
bootstrap = load_install_env()
@@ -1062,6 +1044,78 @@ class InstallEnvTest(unittest.TestCase):
self.assertEqual(uv_path.read_bytes(), b"binary")
def test_uv_download_falls_back_to_omoolab_and_verifies_checksum(self):
bootstrap = load_install_env()
content = io.BytesIO()
with zipfile.ZipFile(content, "w") as archive:
archive.writestr("uv/uv.exe", b"uv-binary")
payload = content.getvalue()
class Response(io.BytesIO):
headers = {"Content-Length": str(len(payload))}
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
destination = root / "uv.tmp"
status = root / "status.json"
urls = []
clock_state = {"now": 0.0}
def clock():
clock_state["now"] += 0.25
return clock_state["now"]
def open_url(request):
urls.append(request.full_url)
if len(urls) == 1:
raise OSError("official unavailable")
return Response(payload)
with patch.object(
bootstrap,
"uv_download_target",
return_value=("test-target", "zip"),
), patch.dict(
bootstrap.UV_ARTIFACTS,
{
"test-target": (
"uv-test.zip",
hashlib.sha256(payload).hexdigest(),
)
},
), patch.dict(
bootstrap._uv_download_openers,
{
bootstrap.UV_OFFICIAL_BASE_URL: SimpleNamespace(
open=open_url
),
bootstrap.UV_MIRROR_BASE_URL: SimpleNamespace(
open=open_url
),
},
), patch.object(
bootstrap,
"write_status",
) as write_status:
result = bootstrap.download_uv_archive(
destination,
status,
0.0,
1.0,
{},
clock=clock,
)
self.assertEqual(result.read_bytes(), payload)
self.assertTrue(urls[0].startswith(bootstrap.UV_OFFICIAL_BASE_URL))
self.assertTrue(urls[1].startswith(bootstrap.UV_MIRROR_BASE_URL))
self.assertEqual(bootstrap.UV_OFFICIAL_CONNECT_TIMEOUT, 3.0)
self.assertEqual(bootstrap.UV_MIRROR_CONNECT_TIMEOUT, 15.0)
messages = [call.args[2] for call in write_status.call_args_list]
self.assertTrue(any("uv (mirror)" in value for value in messages))
self.assertTrue(any("B/s" in value for value in messages))
self.assertFalse(any("left" in value for value in messages))
def test_install_stages_use_only_runtime_packages(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
@@ -1244,15 +1298,13 @@ class InstallEnvTest(unittest.TestCase):
)
self.assertEqual(report, {})
def test_install_uv_extracts_bundled_archive(self):
def test_install_uv_extracts_downloaded_archive(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
uv_path = root / "uv"
bundled = root / "bundled"
bundled.mkdir(parents=True)
target, archive_name = bootstrap.uv_download_target()
archive = bundled / f"uv-{target}.{archive_name}"
archive = root / f"uv-{target}.{archive_name}"
if archive_name.endswith("zip"):
with zipfile.ZipFile(archive, "w") as bundle:
bundle.writestr("uv/uv.exe", b"uv-binary")
@@ -1262,30 +1314,32 @@ class InstallEnvTest(unittest.TestCase):
info = tarfile.TarInfo("uv")
info.size = len(content)
tar.addfile(info, io.BytesIO(content))
def fake_download(destination, *_args):
shutil.copy2(archive, destination)
return destination
with patch.object(
bootstrap,
"bundled_vendor_directory",
return_value=bundled,
"download_uv_archive",
side_effect=fake_download,
):
bootstrap.install_uv(uv_path, str(root / "status.json"))
self.assertEqual(uv_path.read_bytes(), b"uv-binary")
def test_install_uv_raises_when_bundled_missing(self):
def test_install_uv_raises_when_download_fails(self):
bootstrap = load_install_env()
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
uv_path = root / "uv"
bundled = root / "bundled"
bundled.mkdir()
with patch.object(
bootstrap,
"bundled_vendor_directory",
return_value=bundled,
"download_uv_archive",
side_effect=RuntimeError("all uv sources failed"),
):
with self.assertRaisesRegex(
RuntimeError,
"Bundled uv is missing",
"all uv sources failed",
):
bootstrap.install_uv(uv_path, str(root / "status.json"))

View File

@@ -61,21 +61,11 @@ class PackTest(unittest.TestCase):
self.assertIn("service.py", names)
self.assertNotIn("snapform/__init__.py", names)
self.assertNotIn("importer.py", names)
self.assertIn(
f"vendor/{pack.PLATFORMS[platform]}",
names,
)
self.assertFalse(
any(name.startswith("sources/") for name in names)
)
bundled_uv = {
name
for name in names
if name.startswith("vendor/uv-")
}
self.assertEqual(
bundled_uv,
{f"vendor/{pack.PLATFORMS[platform]}"},
self.assertFalse(
any(name.startswith("vendor/") for name in names)
)
self.assertIn(f'platforms = ["{platform}"]', manifest)
self.assertNotIn(

View File

@@ -1,67 +0,0 @@
import hashlib
import importlib.util
import io
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
PROJECT_ROOT = Path(__file__).resolve().parents[1]
def load_prepare_vendor():
specification = importlib.util.spec_from_file_location(
"snapform_prepare_vendor",
PROJECT_ROOT / "prepare_vendor.py",
)
module = importlib.util.module_from_spec(specification)
specification.loader.exec_module(module)
return module
class PrepareVendorTest(unittest.TestCase):
def test_downloads_and_verifies_artifact(self):
prepare = load_prepare_vendor()
content = b"locked artifact"
artifact = {
"filename": "artifact.tar.gz",
"url": "https://example.invalid/artifact.tar.gz",
"sha256": hashlib.sha256(content).hexdigest(),
}
with tempfile.TemporaryDirectory() as directory, patch.object(
prepare.urllib.request,
"urlopen",
return_value=io.BytesIO(content),
):
path = prepare.download_artifact(artifact, Path(directory))
self.assertEqual(path.read_bytes(), content)
def test_rejects_hash_mismatch_without_leaving_partial_file(self):
prepare = load_prepare_vendor()
artifact = {
"filename": "artifact.tar.gz",
"url": "https://example.invalid/artifact.tar.gz",
"sha256": hashlib.sha256(b"expected").hexdigest(),
}
with tempfile.TemporaryDirectory() as directory, patch.object(
prepare.urllib.request,
"urlopen",
return_value=io.BytesIO(b"unexpected"),
):
destination = Path(directory)
with self.assertRaisesRegex(RuntimeError, "SHA-256 mismatch"):
prepare.download_artifact(artifact, destination)
self.assertFalse((destination / artifact["filename"]).exists())
self.assertFalse(
(destination / f"{artifact['filename']}.part").exists()
)
def test_rejects_unknown_platform(self):
prepare = load_prepare_vendor()
with self.assertRaisesRegex(ValueError, "Unsupported platform"):
prepare.prepare(["windows-arm64"])
if __name__ == "__main__":
unittest.main()

View File

@@ -369,7 +369,7 @@ class WorkerTest(unittest.TestCase):
"_verify_model_complete",
return_value=None,
):
args.model_dir = str(Path(directory) / "model")
args.model_dir = str(Path(directory) / "models" / "DA3")
args.status = str(Path(directory) / "status.json")
self.worker.download_model(args)
@@ -456,7 +456,7 @@ class WorkerTest(unittest.TestCase):
"_verify_model_complete",
return_value=None,
):
args.model_dir = str(Path(directory) / "model")
args.model_dir = str(Path(directory) / "models" / "DA3")
args.status = str(Path(directory) / "status.json")
self.worker.download_model(args)
@@ -937,15 +937,21 @@ class WorkerTest(unittest.TestCase):
self.assertEqual(mesh_calls[0][2:], (35.0, True, 0.2))
def test_reconstruct_copies_frames_for_multi_frame_input(self):
inference_inputs = []
fake_da3 = ModuleType("da3_onnx")
fake_da3.create_session = lambda _model_dir, _device: object()
fake_da3.infer = lambda *_args, **_kwargs: SimpleNamespace(
depth=np.full((2, 2, 2), 2.0, dtype=np.float32),
conf=np.ones((2, 2, 2), dtype=np.float32),
extrinsics=np.repeat(np.eye(4, dtype=np.float32)[None, :3], 2, axis=0),
intrinsics=np.repeat(np.eye(3, dtype=np.float32)[None], 2, axis=0),
processed_images=np.zeros((2, 2, 2, 3), dtype=np.uint8),
)
def fake_infer(_session, images, **_options):
inference_inputs.append(images)
return SimpleNamespace(
depth=np.full((1, 2, 2), 2.0, dtype=np.float32),
conf=np.ones((1, 2, 2), dtype=np.float32),
extrinsics=np.eye(4, dtype=np.float32)[None, :3],
intrinsics=np.eye(3, dtype=np.float32)[None],
processed_images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
)
fake_da3.infer = fake_infer
fake_runtime = ModuleType("onnx_runtime")
fake_runtime.provider_label = lambda _session: "CPU"
@@ -979,6 +985,7 @@ class WorkerTest(unittest.TestCase):
show_cameras=0,
gs_views_interval=1,
remove_background_model_dir="",
inference_mode="per_frame",
)
self.worker.reconstruct(args)
@@ -989,6 +996,10 @@ class WorkerTest(unittest.TestCase):
self.assertTrue(
(result_directory / "frame.0002.jpg").is_file()
)
self.assertEqual(len(inference_inputs), 2)
self.assertTrue(all(len(paths) == 1 for paths in inference_inputs))
with np.load(result_directory / "results.npz") as result:
self.assertEqual(result["depth"].shape, (2, 2, 2))
self.assertTrue(
(result_directory / "results.npz").is_file()
)