From 6e6dd60a889942f8e756d1e04ad45c36c3753062 Mon Sep 17 00:00:00 2001 From: nan Date: Mon, 17 Aug 2026 21:36:58 +0800 Subject: [PATCH] chore: Bump version to 0.12.6, update BlendJob wheel to 0.1.8, and revise documentation for model catalog and server integration --- README.md | 2 +- docs/development.md | 7 +- docs/features.md | 2 +- docs/server.md | 12 +- pyproject.toml | 4 +- src/snapform/blender_manifest.toml | 4 +- src/snapform/server/model_catalog.py | 188 +++++++++++++++++++++++++++ src/snapform/server/model_manager.py | 8 +- tests/test_models.py | 14 ++ tests/test_pack.py | 2 +- tests/test_server.py | 25 ++-- uv.lock | 10 +- 12 files changed, 242 insertions(+), 36 deletions(-) create mode 100644 src/snapform/server/model_catalog.py diff --git a/README.md b/README.md index 7ee928f..edaccc5 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ Session 逐帧推理,显存峰值不随视频帧数平方增长;Debug 多帧 ## 常见问题 -- 从 0.12.0–0.12.2 升级到 0.12.3 后,请重新执行一次 `Install Environment`,让 Storage Root 中的 Environment 更新 BlendJob 运行依赖 +- 从 0.12.0–0.12.5 升级到 0.12.6 后,请重新执行一次 `Install Environment`,重建不再单独安装 BlendJob 的 Server Environment - 首次使用需要下载模型,如果下载慢或失败,可在 Image Empty 的 `SnapForm` 右键菜单点击 `Download Required Models`,也可在 `Models` panel 里逐项重试 - 安装 Environment 时 uv 下载失败会自动尝试 OmooLab 镜像;Python packages 需要指定镜像时设置 diff --git a/docs/development.md b/docs/development.md index 866be4e..a6a72d3 100644 --- a/docs/development.md +++ b/docs/development.md @@ -25,7 +25,7 @@ flowchart LR - `job_runtime.py` 以入口字符串声明独立 Server,并配置 Environment、Storage Root 与首次安装后的推荐模型下载 - `server/__init__.py` 创建 `JobServer`、注册公开 Job,并把 `ModelManager` 注册为 Resource - `server/media.py`、`background.py`、`da3.py`、`moge2.py` 负责业务流程;`onnx_*.py` 只处理模型输入、推理和输出恢复 -- 根目录 `model_catalog.py` 是 Blender UI 与独立 Server 共用的纯数据声明;`server/model_download.py`、`server/model_manager.py` 分别负责下载和 Session 生命周期 +- 根目录 `model_catalog.py` 供 Blender UI 与本地就绪检查使用;`server/model_catalog.py` 独立声明下载与推理所需模型,二者的模型键、目录名和 `ready_patterns` 由测试约束一致 - `server/geometry.py` 生成 NPZ、预览、点云和纹理网格;所有任务产物写入 Runtime 分配的 opaque-ID Job 目录 功能细节继续按主题维护:[Depth Plane](depth-plane.md)、[Surface](surface.md)、[输出格式](outputs.md) 和 [Job Server](server.md)。 @@ -61,6 +61,7 @@ flowchart LR │ │ ├── geometry.py │ │ ├── media.py │ │ ├── model_download.py +│ │ ├── model_catalog.py │ │ ├── model_manager.py │ │ ├── moge2.py │ │ ├── onnx_ben2.py @@ -68,7 +69,7 @@ flowchart LR │ │ ├── onnx_moge2.py │ │ └── onnx_runtime.py │ └── wheels/ -│ └── blendjob-0.1.6-py3-none-any.whl +│ └── blendjob-0.1.8-py3-none-any.whl ├── tests/ └── tools/ ``` @@ -84,7 +85,7 @@ uv run pytest tests/test_addon.py tests/test_manifest.py tests/test_pack.py -v uv run python pack.py ``` -`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.3.windows-x64.zip`。从 PyPI 下载并更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试: +`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.6.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试: ```bash uv lock --refresh diff --git a/docs/features.md b/docs/features.md index d4c8805..33c0531 100644 --- a/docs/features.md +++ b/docs/features.md @@ -55,7 +55,7 @@ ## 使用 1. 运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、 - `linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.3.macos-arm64.zip`)。 + `linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.6.macos-arm64.zip`)。 安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,官方源建连超过 3 秒后回退 OmooLab 镜像,并校验固定 SHA-256。 2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。 3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能 diff --git a/docs/server.md b/docs/server.md index d1ad3d0..092f42f 100644 --- a/docs/server.md +++ b/docs/server.md @@ -22,14 +22,14 @@ Environment、模型和任务结果默认保存在 `~/.snapform`: 不同 Blender 版本可共享此目录。也可以在 Extension Preferences 的 `Storage Root` 中修改位置。连接端口与 Instance ID 只保存在启动它们的 Blender 进程内存中。 -Blender 侧的通用 HTTP Runtime 由 `src/snapform/wheels/` 中随扩展分发的 BlendJob package 提供;独立 Environment 由 Runtime 从 PyPI 自动安装相同版本。`JobServer` 包装 FastAPI 并提供 Job、FIFO Queue 与 Resource API,`JobClient` 是无第三方依赖的 Blender 侧访问器。Blender 侧只把 `server/__init__.py:server` 入口字符串交给 Runtime,不导入 `server` package;独立进程启动后才取得其中的 `server` 实例、注册全部公开 Job,并创建 `model_manager` Resource。 +Blender Client 与独立 Server 的通用 HTTP Runtime 都复用 `src/snapform/wheels/` 中随扩展分发的同一份 BlendJob package;Server Environment 不再从 PyPI 安装第二份。`JobServer` 包装 FastAPI 并提供 Job、FIFO Queue 与 Resource API,`JobClient` 是无第三方依赖的 Blender 侧访问器。Blender 侧只把 `server/__init__.py:server` 入口字符串交给 Runtime,不导入 `server` package;独立进程启动后才取得其中的 `server` 实例、注册全部公开 Job,并创建 `model_manager` Resource。 SnapForm 根 package 不再有 `client.py`、`environment.py`、`storage.py` 或 `models.py`。这些文件会制造并不存在的业务边界: - Storage Root、`.venv`、Manifest、安装状态与日志路径由 `JobRuntime` 唯一管理; - `InstallEnvironment` 由 `runtime.operator_classes()` 提供,Python 与 Job 自身的额外 packages 直接声明在传给 Runtime 的 `environment` 字典中;BlendJob 不需要重复声明; -- 模型 catalog 由 `model_catalog.py` 统一声明;下载和 ONNX Session 属于 Server,分别位于 `model_download.py` 与 `model_manager.py`; -- Blender UI 复用同一 catalog,直接检查 Storage Root 下的模型必需文件,不为显示下载状态启动 Server;已加载 Session 状态仍读取现有 Server Snapshot。 +- Blender Client 使用根目录 `model_catalog.py` 构造 UI 并检查 Storage Root 下的模型必需文件; +- 独立 Server 使用 `server/model_catalog.py` 下载和验证模型,不依赖 Extension 根 package。两份声明的共享存储字段由测试保持一致,已加载 Session 状态仍读取 Server Snapshot。 BlendJob 的 `JobClient` 只是无依赖 HTTP transport,不是 SnapForm 业务抽象,也不向 Operator 暴露 `JobRequest`。 @@ -57,13 +57,13 @@ Server 端没有 `execute_*`、`*_result` 或 CLI worker 中转。全部 `@serve - ONNX Runtime 1.24.4;Windows 使用 `onnxruntime-directml` - NumPy 2.4.2、Pillow 12.1.1、PyAV 17.0.0、trimesh 4.11.2 -- BlendJob 0.1.6、FastAPI 0.139.2、Uvicorn 0.51.0、huggingface-hub 1.4.1 +- FastAPI 0.139.2、Uvicorn 0.51.0、huggingface-hub 1.4.1 运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。 Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。 -`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明、BlendJob 版本与基础 Server packages 的 Hash。Python、packages、平台 packages、BlendJob 版本或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。 +`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。 ## 模型 @@ -80,7 +80,7 @@ Image Empty 的右键菜单会读取 `model_manager` 状态;缺少 MoGe-2 ViT- 只下载缺失项,并把多个下载的进度合并为单次 0–100%。必需模型补齐前,右键菜单中的 `Convert to Depth Plane` 保持禁用。 -`model_manager` Resource 在 Server 内持有模型 catalog、磁盘就绪状态和内存 Session。Blender UI 使用 catalog 声明的目录名与 `ready_patterns` 直接检查 `/models/`,因此展开模型面板、打开转换对话框或 Image Empty 右键菜单都不会仅为状态检测启动 Server。真正下载或推理时,Server 仍通过 `ModelManager.ready()` 重新验证文件。`ModelSessionCache` 保留 BEN2,并在 DA3 与 MoGe-2 之间只保留最近使用的一种 Depth Session。`GET /resources/model_manager` 继续返回 catalog、下载状态和当前加载状态;`POST /resources/model_manager/clear` 只允许在没有 Active 或 Queued Job 时执行。 +`model_manager` Resource 在 Server 内持有磁盘就绪状态和内存 Session。Blender UI 使用 Client catalog 声明的目录名与 `ready_patterns` 直接检查 `/models/`,因此展开模型面板、打开转换对话框或 Image Empty 右键菜单都不会仅为状态检测启动 Server。真正下载或推理时,Server 仍按自身 catalog 通过 `ModelManager.ready()` 重新验证文件。`ModelSessionCache` 保留 BEN2,并在 DA3 与 MoGe-2 之间只保留最近使用的一种 Depth Session。`GET /resources/model_manager` 只返回当前加载状态;`POST /resources/model_manager/clear` 只允许在没有 Active 或 Queued Job 时执行。 DA3 ONNX 使用模型的 camera decoder 恢复外参与内参,不提供 ray-pose 参数。MoGe-2 的 metric depth、normal、mask、normalized intrinsics,以及 BEN2 的 Alpha,均在 NumPy 后处理中恢复。 diff --git a/pyproject.toml b/pyproject.toml index f552322..37d3508 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "snapform" -version = "0.12.3" +version = "0.12.6" description = "Fast image-guided structure building tools for Blender" readme = "README.md" requires-python = ">=3.11" @@ -14,4 +14,4 @@ dev = ["numpy", "pillow", "pytest"] package = false [tool.uv.sources] -blendjob = { path = "src/snapform/wheels/blendjob-0.1.6-py3-none-any.whl" } +blendjob = { path = "src/snapform/wheels/blendjob-0.1.8-py3-none-any.whl" } diff --git a/src/snapform/blender_manifest.toml b/src/snapform/blender_manifest.toml index f648da0..24c1028 100644 --- a/src/snapform/blender_manifest.toml +++ b/src/snapform/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "snapform" -version = "0.12.3" +version = "0.12.6" name = "SnapForm" tagline = "Build spatial structures from images, fast" maintainer = "OmooLab" @@ -10,7 +10,7 @@ type = "add-on" blender_version_min = "4.5.0" platforms = ["windows-x64", "macos-arm64", "linux-x64"] license = ["SPDX:GPL-3.0-or-later"] -wheels = ["./wheels/blendjob-0.1.6-py3-none-any.whl"] +wheels = ["./wheels/blendjob-0.1.8-py3-none-any.whl"] [permissions] network = "Access the local Job Server and download environment dependencies and model weights" diff --git a/src/snapform/server/model_catalog.py b/src/snapform/server/model_catalog.py new file mode 100644 index 0000000..4e5260b --- /dev/null +++ b/src/snapform/server/model_catalog.py @@ -0,0 +1,188 @@ +from dataclasses import dataclass + + +@dataclass(frozen=True) +class ModelSpec: + key: str + label: str + directory_name: str + repository: str + license_name: str + description: str + supports_gs: bool + download_patterns: tuple[str, ...] = () + ready_patterns: tuple[str, ...] = ("config.json", "*.safetensors") + enabled: bool = True + r2_directory: str = "" + r2_files: tuple[tuple[str, int, str], ...] = () + + +MODELS = { + "SMALL": ModelSpec( + key="SMALL", + label="DA3 Small", + directory_name="DA3-SMALL", + repository="depth-anything/DA3-SMALL", + license_name="Apache 2.0", + description="Fastest any-view model using ONNX Runtime", + supports_gs=False, + download_patterns=("model.onnx",), + ready_patterns=("model.onnx",), + r2_directory="DA3-SMALL", + r2_files=( + ("model.onnx", 105484029, "7a9d81475b3e2334ace0d6d2c2a816b3c93081437e1866cc434ce1789125f631"), + ), + ), + "BASE": ModelSpec( + key="BASE", + label="DA3 Base", + directory_name="DA3-BASE", + repository="depth-anything/DA3-BASE", + license_name="Apache 2.0", + description="Balanced any-view model using ONNX Runtime", + supports_gs=False, + download_patterns=("model.onnx",), + ready_patterns=("model.onnx",), + r2_directory="DA3-BASE", + r2_files=( + ("model.onnx", 412856697, "31f38fc4990539d7902ba267924b9731db1acb00d840c248a60b1865810ff9e6"), + ), + ), + "GIANT_1_1": ModelSpec( + key="GIANT_1_1", + label="DA3 Giant 1.1", + directory_name="da3_giant_1_1", + repository="depth-anything/DA3-GIANT-1.1", + license_name="CC BY-NC 4.0", + description="Non-commercial Giant model with Gaussian head", + supports_gs=True, + enabled=False, + ), + "NESTED_GIANT_LARGE_1_1": ModelSpec( + key="NESTED_GIANT_LARGE_1_1", + label="DA3 Nested Giant Large 1.1", + directory_name="da3_nested_giant_large_1_1", + repository="depth-anything/DA3NESTED-GIANT-LARGE-1.1", + license_name="CC BY-NC 4.0", + description="Non-commercial nested model with metric depth and Gaussian head", + supports_gs=True, + enabled=False, + ), +} + +BEN2_MODEL = ModelSpec( + key="BEN2_BASE", + label="BEN2 Base", + directory_name="BEN2-ONNX", + repository="onnx-community/BEN2-ONNX", + license_name="Apache 2.0", + description="Fast high-quality FP16 ONNX background removal model", + supports_gs=False, + download_patterns=("onnx/model_fp16.onnx",), + ready_patterns=("onnx/model_fp16.onnx",), + r2_directory="BEN2-ONNX", + r2_files=(( + "onnx/model_fp16.onnx", + 219121675, + "dfdc25f421f32a0d1268e0f2ff2153d340e8f1d52d3dd16f5dc33c1ce85cedf1", + ),), +) + + +MOGE2_MODELS = { + "MOGE2_VITS_NORMAL": ModelSpec( + key="MOGE2_VITS_NORMAL", + label="MoGe-2 ViT-S Normal", + directory_name="moge-2-vits-normal-onnx", + repository="Ruicheng/moge-2-vits-normal-onnx", + license_name="MIT", + description="Fastest MoGe-2 model using ONNX Runtime", + supports_gs=False, + download_patterns=("model.onnx",), + ready_patterns=("model.onnx",), + r2_directory="moge-2-vits-normal-onnx", + r2_files=(( + "model.onnx", + 140852051, + "24eacb5dc7a2c54c7bc98f7de085ffbed79ad006ea5b664c2c2cdc02ff3a52f0", + ),), + ), + "MOGE2_VITB_NORMAL": ModelSpec( + key="MOGE2_VITB_NORMAL", + label="MoGe-2 ViT-B Normal", + directory_name="moge-2-vitb-normal-onnx", + repository="Ruicheng/moge-2-vitb-normal-onnx", + license_name="MIT", + description="Balanced MoGe-2 model using ONNX Runtime", + supports_gs=False, + download_patterns=("model.onnx",), + ready_patterns=("model.onnx",), + r2_directory="moge-2-vitb-normal-onnx", + r2_files=(( + "model.onnx", + 419411850, + "bbf14e07a30f11e69d36ab861590123f5598ababcbc8946a063eb4a966f35a21", + ),), + ), + "MOGE2_VITL_NORMAL": ModelSpec( + key="MOGE2_VITL_NORMAL", + label="MoGe-2 ViT-L Normal", + directory_name="moge-2-vitl-normal-onnx", + repository="Ruicheng/moge-2-vitl-normal-onnx", + license_name="MIT", + description="Highest-quality MoGe-2 model using ONNX Runtime", + supports_gs=False, + download_patterns=("model.onnx",), + ready_patterns=("model.onnx",), + r2_directory="moge-2-vitl-normal-onnx", + r2_files=(( + "model.onnx", + 1324265014, + "afbc4ccc3450298f3afb35b90f015f4c4f552dea21dc6470d5f7b78b77e2d751", + ),), + ), +} +MOGE2_MODEL = MOGE2_MODELS["MOGE2_VITS_NORMAL"] +DOWNLOADABLE_MODELS = { + **MODELS, + BEN2_MODEL.key: BEN2_MODEL, + **MOGE2_MODELS, +} +MODEL_NUMBERS = { + model_key: number for number, model_key in enumerate(MODELS) +} + + +def get_model(model_key): + try: + return MODELS[model_key] + except KeyError as error: + raise ValueError(f"Unknown DA3 model: {model_key}") from error + + +def get_downloadable_model(model_key): + try: + return DOWNLOADABLE_MODELS[model_key] + except KeyError as error: + raise ValueError(f"Unknown model: {model_key}") from error + + +def model_directory_name(model_key): + """Return the model's folder name inside the shared models directory.""" + return get_downloadable_model(model_key).directory_name + + +def model_record(model, ready=False): + """Return public model metadata exposed by the Models Resource.""" + return { + "key": model.key, + "label": model.label, + "description": model.description, + "license": model.license_name, + "supports_gs": model.supports_gs, + "enabled": model.enabled, + "family": "moge2" if model.key.startswith("MOGE2_") else ( + "ben2" if model.key == BEN2_MODEL.key else "da3" + ), + "ready": bool(ready), + } diff --git a/src/snapform/server/model_manager.py b/src/snapform/server/model_manager.py index 09aaa9b..6b43439 100644 --- a/src/snapform/server/model_manager.py +++ b/src/snapform/server/model_manager.py @@ -3,7 +3,7 @@ import time from pathlib import Path from types import SimpleNamespace -import model_catalog +from . import model_catalog from .model_download import download_model @@ -144,11 +144,7 @@ class ModelManager: return self.sessions.get_moge2(model_dir, device) def snapshot(self): - catalog = [ - model_catalog.model_record(model, self.ready(model.key)) - for model in model_catalog.DOWNLOADABLE_MODELS.values() - ] - return {"loaded": self.sessions.snapshot(), "catalog": catalog} + return {"loaded": self.sessions.snapshot()} def clear(self): self.sessions.close() diff --git a/tests/test_models.py b/tests/test_models.py index 7f63876..de314f8 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -23,6 +23,20 @@ class ModelTest(unittest.TestCase): "snapform_models", "src/snapform/model_catalog.py", ) + cls.server_models = load_module( + "snapform_server_models", + "src/snapform/server/model_catalog.py", + ) + + def test_client_and_server_catalogs_share_storage_contract(self): + client = self.models.DOWNLOADABLE_MODELS + server = self.server_models.DOWNLOADABLE_MODELS + + self.assertEqual(set(client), set(server)) + for key in client: + with self.subTest(key=key): + self.assertEqual(client[key].directory_name, server[key].directory_name) + self.assertEqual(client[key].ready_patterns, server[key].ready_patterns) def test_base_model_is_default_friendly_apache_model(self): model = self.models.get_model("BASE") diff --git a/tests/test_pack.py b/tests/test_pack.py index c080b0c..5bd31ba 100644 --- a/tests/test_pack.py +++ b/tests/test_pack.py @@ -73,7 +73,7 @@ class PackTest(unittest.TestCase): self.assertNotIn("server/jobs.py", names) self.assertIn("server/__init__.py", names) self.assertIn( - "wheels/blendjob-0.1.6-py3-none-any.whl", + "wheels/blendjob-0.1.8-py3-none-any.whl", names, ) self.assertFalse( diff --git a/tests/test_server.py b/tests/test_server.py index 69ca6a8..9bb08b4 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -1,4 +1,4 @@ -import importlib +import importlib.util import sys import tempfile import threading @@ -13,12 +13,15 @@ PROJECT_ROOT = Path(__file__).resolve().parents[1] def load_server_module(): - addon_directory = PROJECT_ROOT / "src" / "snapform" - sys.path.insert(0, str(addon_directory)) - try: - return importlib.import_module("server") - finally: - sys.path.remove(str(addon_directory)) + server_directory = PROJECT_ROOT / "src" / "snapform" / "server" + specification = importlib.util.spec_from_file_location( + "server", server_directory / "__init__.py", + submodule_search_locations=[str(server_directory)], + ) + module = importlib.util.module_from_spec(specification) + sys.modules["server"] = module + specification.loader.exec_module(module) + return module class FakeCuda: @@ -70,8 +73,12 @@ class ServerTest(unittest.TestCase): with tempfile.TemporaryDirectory() as directory: manager = self.server_module.ModelManager(Path(directory)) snapshot = manager.snapshot() - self.assertIn("catalog", snapshot) - self.assertTrue(snapshot["catalog"]) + self.assertEqual( + snapshot, + {"loaded": {"ben2": "", "da3": "", "moge2": ""}}, + ) + self.assertIn("server.model_catalog", sys.modules) + self.assertNotIn("model_catalog", sys.modules) def create_cache(self): cache = self.server_module.ModelSessionCache.__new__( diff --git a/uv.lock b/uv.lock index ada0c72..72b50af 100644 --- a/uv.lock +++ b/uv.lock @@ -8,10 +8,10 @@ resolution-markers = [ [[package]] name = "blendjob" -version = "0.1.6" -source = { path = "src/snapform/wheels/blendjob-0.1.6-py3-none-any.whl" } +version = "0.1.8" +source = { path = "src/snapform/wheels/blendjob-0.1.8-py3-none-any.whl" } wheels = [ - { filename = "blendjob-0.1.6-py3-none-any.whl", hash = "sha256:ed7c0ee4a0c952c25a7b6606bcd860b5c782ecb04ae6f9e7b1c475a03ab9c6b1" }, + { filename = "blendjob-0.1.8-py3-none-any.whl", hash = "sha256:9e94b42dfe0dde62702dff119243d5db3bfd4b4b3f0553191aaada1452b4338d" }, ] [package.metadata] @@ -305,7 +305,7 @@ wheels = [ [[package]] name = "snapform" -version = "0.12.3" +version = "0.12.6" source = { virtual = "." } dependencies = [ { name = "blendjob" }, @@ -320,7 +320,7 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "blendjob", path = "src/snapform/wheels/blendjob-0.1.6-py3-none-any.whl" }] +requires-dist = [{ name = "blendjob", path = "src/snapform/wheels/blendjob-0.1.8-py3-none-any.whl" }] [package.metadata.requires-dev] dev = [