chore: Update version to 0.12.11, upgrade BlendJob wheel to 0.1.13, and enhance documentation for environment installation and model management
This commit is contained in:
14
README.md
14
README.md
@@ -16,17 +16,18 @@ SnapForm 是一个 Blender 扩展,可以把一张照片、一段视频或一
|
||||
2. 打开 Blender,进入 `Preferences > Extensions`
|
||||
3. 点击 `Install from Disk`,选择下载的压缩包
|
||||
4. 在扩展偏好设置中确认 Storage Root 与 Device(保持默认即可);Device 默认取本机
|
||||
最快的 DirectML/CoreML provider,否则 CPU。下载超时会自动重试国内镜像,无需手动配置
|
||||
5. 打开 3D 视图右侧的 `SnapForm` 面板,点击 `Install Environment`
|
||||
最快的 DirectML/CoreML provider,否则 CPU
|
||||
5. 打开 3D 视图右侧的 `SnapForm` 面板:使用官方源点击 `Install Environment`,使用国内镜像点击 `Install Environment (Mirror)`
|
||||
|
||||
扩展不内置 uv 或 AI 运行库。首次使用会先下载并校验固定版本的 uv,随后自动安装轻量的 ONNX Runtime 环境,再下载 `MoGe-2 ViT-S Normal` 和
|
||||
`BEN2 Base`;不安装 PyTorch,也不下载各模型的 Python 源码包。其他 AI 模型可在
|
||||
`Models` panel 中按需下载,请保持网络畅通,首次安装需要一些时间。
|
||||
安装时 Environment、MoGe-2 和 BEN2 各自使用独立的 0–100% 进度;Environment 这一轮内部依次完成 uv、Python 环境和 packages。进入后续模型时进度归零,但不显示 `2/2` 等阶段编号。模型源发生瞬时错误时会切换到另一来源继续下载。
|
||||
Environment Bootstrap 使用连续的 0–100%:uv 为 1–20%、Python 为 20–50%、packages 为 50–98%,最终验证为 98–100%。三个阶段显示真实的 `1/3`、`2/3`、`3/3`,下载占各阶段的 84–87%;开始与最终验证不显示编号,单阶段状态不显示 `1/1`。官方按钮固定使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮固定使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI,两种方式都不竞速或自动切换。MoGe-2 与 BEN2 属于 post-install,各自使用独立进度,不计入 Environment 的这轮 0–100%,其模型下载源逻辑不受按钮选择影响。
|
||||
任一必需模型在两个来源都下载失败时,`Install Environment` 会明确报错;Environment 本身仍会保留。
|
||||
选中 Image Empty 后,右键 `SnapForm` 子菜单会在缺少 `MoGe-2 ViT-S Normal` 或 `BEN2 Base`
|
||||
时显示 `Download Required Models`,一次补齐缺失项,不会重复下载已经就绪的模型。
|
||||
必需模型补齐前,菜单中的 `Convert to Depth Plane` 会保持禁用。
|
||||
Environment 未安装时,同一位置显示两个安装按钮。Environment 或必需模型未就绪时,
|
||||
菜单仍显示 `Convert to Depth Plane`、`Convert to Surface` 和 `Remove Background`,但三个入口都会保持禁用。
|
||||
这些 UI 状态直接检查 Storage Root 的 `models/` 文件,不会仅因打开菜单或面板而启动 Job Server。
|
||||
|
||||
## 快速上手
|
||||
@@ -84,11 +85,10 @@ Session 逐帧推理,显存峰值不随视频帧数平方增长;Debug 多帧
|
||||
|
||||
## 常见问题
|
||||
|
||||
- 从 0.12.0–0.12.5 升级到 0.12.6 后,请重新执行一次 `Install Environment`,重建不再单独安装 BlendJob 的 Server Environment
|
||||
- 从 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 需要指定镜像时设置
|
||||
`UV_INDEX_URL` 后重新安装
|
||||
- 安装 Environment 时下载状态会显示平均速度;网络适合海外源时选择 `Install Environment`,国内网络选择 `Install Environment (Mirror)`。每个按钮固定使用对应来源,失败时不会自动切换
|
||||
- 默认模型下载会在 Hugging Face 与 OmooLab 镜像之间自动回退;某一来源暂时断线不会阻止后续模型尝试另一来源
|
||||
- 模型直链建连超过 10 秒,或连接后连续 60 秒没有收到数据,会切换到下一来源;不限制总下载时长
|
||||
- 找不到右键入口时,请确认选中了 Empty,且显示方式为 Image
|
||||
|
||||
@@ -69,7 +69,7 @@ flowchart LR
|
||||
│ │ ├── onnx_moge2.py
|
||||
│ │ └── onnx_runtime.py
|
||||
│ └── wheels/
|
||||
│ └── blendjob-0.1.8-py3-none-any.whl
|
||||
│ └── blendjob-0.1.13-py3-none-any.whl
|
||||
├── tests/
|
||||
└── tools/
|
||||
```
|
||||
@@ -85,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.6.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试:
|
||||
`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.11.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试:
|
||||
|
||||
```bash
|
||||
uv lock --refresh
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
- Image Empty 右键菜单可直接运行 BEN2,保留原图并换用新建的透明背景图片
|
||||
- 缺少必需的 `MoGe-2 ViT-S Normal` 或 `BEN2 Base` 时,Image Empty 的 `SnapForm`
|
||||
右键子菜单显示 `Download Required Models`;单次任务只下载缺失项,并在补齐前禁用
|
||||
`Convert to Depth Plane`
|
||||
`Convert to Depth Plane`、`Convert to Surface` 和 `Remove Background`。Environment 未安装时,
|
||||
同一位置改为显示 `Install Environment` 与 `Install Environment (Mirror)`,三个禁用的功能入口仍然可见
|
||||
- 菜单、模型面板和转换对话框直接检查 Storage Root 的 `models/` 必需文件,显示模型状态时不会启动 Job Server
|
||||
- Image Empty 支持视频与序列帧输入:`Convert to Depth Plane` 逐帧估计深度,
|
||||
`Remove Background` 逐帧抠图,入口保持不变。DA3 转换时每帧独立执行 ONNX 推理并复用模型 Session;
|
||||
@@ -55,20 +56,19 @@
|
||||
## 使用
|
||||
|
||||
1. 运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、
|
||||
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.6.macos-arm64.zip`)。
|
||||
安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,官方源建连超过 3 秒后回退 OmooLab 镜像,并校验固定 SHA-256。
|
||||
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.11.macos-arm64.zip`)。
|
||||
安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,并校验固定 SHA-256。官方安装按钮使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI。
|
||||
2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。
|
||||
3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能
|
||||
(Image Empty 右键菜单)使用的推理设备,默认取本机最快的 DirectML/CoreML provider,否则 CPU,且只列出
|
||||
本机支持的设备。
|
||||
4. 在 `Job Server` 点击 `Install Environment`。首次安装会下载 Python、ONNX Runtime、FastAPI、Uvicorn;
|
||||
4. 在 `Job Server` 选择 `Install Environment` 或 `Install Environment (Mirror)`。首次安装会下载 Python、ONNX Runtime、FastAPI、Uvicorn;
|
||||
uv 由 Job Runtime 的 Environment 安装器下载并校验,DA3、BEN2 与 MoGe 不安装源码包,
|
||||
并在 Environment 安装后自动下载 `MoGe-2 ViT-S Normal` 和 FP16 `BEN2 Base` 推荐模型。首次安装
|
||||
依次显示 `Environment`、`MoGe-2 ViT-S Normal` 与 `BEN2 Base`;每项都独立从 0 走到 100%,
|
||||
进入下一项时进度条归零,模型 post-install 不显示 `2/2` 等阶段编号。uv、Python Environment 与 packages 合并在第一轮,
|
||||
Environment Bootstrap 的 0–100% 只包含 uv、Python 与 packages:三阶段显示 `1/3`、`2/3`、`3/3`,范围分别为 1–20%、20–50%、50–98%,最终验证使用 98–100%。下载占各阶段的 84–87%;开始与验证不显示编号,单阶段状态不显示 `1/1`。
|
||||
两个默认模型由普通的 `post_install(runtime)` 发起正式 Server Job。
|
||||
安装完成后直接写入 Environment Manifest,不再额外导入全部组件进行验证;`DA3 Base` 等其他模型可稍后在 `Models` 面板按需下载。
|
||||
依赖安装显示当前安装输出;模型下载显示速度,例如
|
||||
Environment packages 使用固定的 Resolving、Downloading 与 Installing 状态,原始 uv 输出写入 `install.log`。模型下载显示速度,例如
|
||||
`Downloading DA3 Small 14.2 MB/s`;镜像来源显示为
|
||||
`Downloading DA3 Small (mirror) 14.2 MB/s`。下载按字节连续推进,重试或切换镜像时进度不会倒退。
|
||||
Environment 安装阶段与 `Models` 面板单独下载使用相同的模型名称格式,不会改写为通用的 `Downloading packages`。
|
||||
@@ -76,8 +76,7 @@
|
||||
可稍后在 Image Empty 的 `SnapForm` 右键菜单用 `Download Required Models` 一次补齐缺失的
|
||||
MoGe-2 ViT-S 与 BEN2,也可在 `Models` 面板单独补下。
|
||||
安装不做启动网络预检;每个模型都先尝试 Hugging Face,失败后尝试 OmooLab R2,两者都失败时当前 Job 失败。
|
||||
下载源不写入持久状态;需要手动指定依赖镜像时可通过
|
||||
`UV_INDEX_URL` 环境变量覆盖。
|
||||
官方安装固定使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像安装固定使用 CNB uv Release、`python-build-standalone` 镜像和清华 PyPI。两种方式都不测速、不竞速、不自动切换,镜像配置只注入当前安装子进程。模型仍按自身的 Hugging Face 与 OmooLab R2 逻辑下载。
|
||||
5. 在 3D View 按 `N`,打开 `SnapForm` 页签。
|
||||
6. 如需其他模型,展开 `Models` panel,找到模型并点击该行的 `Download`。
|
||||
7. 展开默认收起的 `Debug`,选择 `Output Type` 和本次执行使用的模型。
|
||||
|
||||
@@ -87,13 +87,14 @@ DA3 ONNX 使用模型的 camera decoder 恢复外参与内参,不提供 ray-po
|
||||
|
||||
## 安装网络与镜像
|
||||
|
||||
安装器直接开始工作,不做阻塞式网络预检。packages 由 uv 安装,可用 `UV_INDEX_URL` 显式指定镜像。Windows 系统代理会注入子进程;同时支持 `HTTP_PROXY`、`HTTPS_PROXY` 与 `ALL_PROXY`。
|
||||
安装器不做带宽测速、来源竞速或失败后的自动切换。`Install Environment` 固定使用 Astral GitHub Release、uv 默认 Python 下载源和 PyPI;`Install Environment (Mirror)` 固定使用 CNB uv Release、CNB `python-build-standalone` 与清华 PyPI。两个 uv 来源使用同一固定版本和 SHA-256;下载压缩包只作临时文件,安装完成后删除。
|
||||
|
||||
uv 默认从 Astral 的 GitHub Release 下载,失败后回退到 `https://mirror.omoolab.xyz/uv/<version>/`。两个来源使用相同的连续字节进度并共用固定 SHA-256 校验。下载压缩包只作临时文件,提取完成后删除。模型下载不经过 GitHub。
|
||||
镜像模式只在当前安装子进程中设置 `UV_PYTHON_INSTALL_MIRROR` 与 `UV_INDEX_URL`,不修改用户环境变量。Windows 系统代理会注入子进程;同时支持 `HTTP_PROXY`、`HTTPS_PROXY` 与 `ALL_PROXY`。post-install 模型下载保持 Hugging Face 与 OmooLab R2 的独立来源逻辑,不读取 Environment 安装模式。
|
||||
|
||||
Environment Bootstrap 的单次 0–100% 只包含 uv、Python 与 packages,不包含后续模型下载。开始显示 `Starting installation ...`;uv 使用 1–20%,显示 `1/3 Starting uv download ...`、`1/3 Downloading uv [速度]` 或镜像形式、`1/3 Installing uv ...`;Python 使用 20–50%,显示 Checking、Downloading、Installing,镜像下载带 `(mirror)`;packages 使用 50–98%,显示 Resolving、`Downloading packages [已开始数]/[解析总数]`、Installing,镜像模式带 `(mirror)`;98–100% 显示 `Verifying installation ...`。三个下载区间分别占对应阶段的 84–87%,缓存命中时直接跳过。单阶段状态不显示 `1/1`。
|
||||
|
||||
首次安装依次显示 Environment、MoGe-2 下载与 BEN2 下载,每项分别使用 0–100%。Environment 由 Job Runtime 依次完成 uv 下载、Python Environment 创建与 packages 安装,uv 不再占用独立一轮进度。两个模型下载由 SnapForm 的 `post_install(runtime)` 通过 `runtime.request()` 顺序发起;切换模型时进度归零,但不显示 `2/2` 等阶段编号。
|
||||
任一 post-install 模型失败时,Environment 文件仍然保留,但安装 Operator 返回失败并显示聚合后的模型错误,
|
||||
不再把带 `post_install_error` 的终态报告为成功。
|
||||
任一 post-install 模型失败时,Environment 文件仍然保留,但安装 Operator 返回失败并显示原始模型错误。
|
||||
|
||||
## 生命周期
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "snapform"
|
||||
version = "0.12.6"
|
||||
version = "0.12.11"
|
||||
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.8-py3-none-any.whl" }
|
||||
blendjob = { path = "src/snapform/wheels/blendjob-0.1.13-py3-none-any.whl" }
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import bpy
|
||||
|
||||
from .blender import depth_plane as image_depth
|
||||
from .blender import surface as image_mesh
|
||||
from .blender.depth_plane import (
|
||||
ConvertToDepthPlane,
|
||||
RemoveImageBackground,
|
||||
|
||||
@@ -1970,12 +1970,13 @@ class RemoveImageBackground(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
from ..job_runtime import runtime
|
||||
from ..properties import BEN2_MODEL_KEY, model_ready
|
||||
from ..properties import required_models_ready
|
||||
|
||||
return (
|
||||
is_image_empty(context.object)
|
||||
and runtime.environment_ready()
|
||||
and not runtime.server_busy()
|
||||
and model_ready(BEN2_MODEL_KEY)
|
||||
and required_models_ready()
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
@@ -2045,10 +2046,13 @@ class ConvertToDepthPlane(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
from ..job_runtime import runtime
|
||||
from ..properties import required_models_ready
|
||||
|
||||
return (
|
||||
is_image_empty(context.object)
|
||||
and runtime.environment_ready()
|
||||
and not runtime.server_busy()
|
||||
and required_models_ready()
|
||||
)
|
||||
|
||||
def invoke(self, context, _event):
|
||||
@@ -2170,35 +2174,46 @@ class SnapFormImageMenu(bpy.types.Menu):
|
||||
|
||||
layout = self.layout
|
||||
layout.operator_context = "INVOKE_DEFAULT"
|
||||
if not runtime.environment_ready():
|
||||
environment_ready = runtime.environment_ready()
|
||||
if not environment_ready:
|
||||
layout.operator(
|
||||
"snapform.install_environment",
|
||||
text="Install Environment",
|
||||
icon="IMPORT",
|
||||
)
|
||||
return
|
||||
layout.operator(
|
||||
"snapform.install_environment_mirror",
|
||||
text="Install Environment (Mirror)",
|
||||
icon="IMPORT",
|
||||
)
|
||||
models_ready = required_models_ready()
|
||||
if not models_ready:
|
||||
if environment_ready and not models_ready:
|
||||
layout.operator(
|
||||
"snapform.download_required_models",
|
||||
text="Download Required Models",
|
||||
icon="IMPORT",
|
||||
)
|
||||
features_ready = environment_ready and models_ready
|
||||
if not features_ready:
|
||||
layout.separator()
|
||||
depth_plane_row = layout.row()
|
||||
depth_plane_row.enabled = models_ready
|
||||
depth_plane_row.enabled = features_ready
|
||||
depth_plane_row.operator(
|
||||
"snapform.convert_to_depth_plane",
|
||||
text="Convert to Depth Plane",
|
||||
icon="MOD_DISPLACE",
|
||||
)
|
||||
layout.operator(
|
||||
surface_row = layout.row()
|
||||
surface_row.enabled = features_ready
|
||||
surface_row.operator(
|
||||
"snapform.convert_to_surface",
|
||||
text="Convert to Surface",
|
||||
icon="MESH_GRID",
|
||||
)
|
||||
layout.separator()
|
||||
layout.operator(
|
||||
background_row = layout.row()
|
||||
background_row.enabled = features_ready
|
||||
background_row.operator(
|
||||
"snapform.remove_image_background",
|
||||
text="Remove Background",
|
||||
icon="IMAGE_ALPHA",
|
||||
|
||||
@@ -598,18 +598,14 @@ class ConvertToSurface(bpy.types.Operator):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
from ..job_runtime import runtime
|
||||
from ..properties import model_catalog
|
||||
from ..properties import required_models_ready
|
||||
|
||||
return (
|
||||
is_image_empty(context.object)
|
||||
and video_total_frames(context.object.data) == 1
|
||||
and runtime.environment_ready()
|
||||
and not runtime.server_busy()
|
||||
and any(
|
||||
model.get("ready")
|
||||
and model.get("enabled")
|
||||
and model.get("family") in {"moge2", "da3"}
|
||||
for model in model_catalog()
|
||||
)
|
||||
and required_models_ready()
|
||||
)
|
||||
|
||||
def invoke(self, context, _event):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "snapform"
|
||||
version = "0.12.6"
|
||||
version = "0.12.11"
|
||||
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.8-py3-none-any.whl"]
|
||||
wheels = ["./wheels/blendjob-0.1.13-py3-none-any.whl"]
|
||||
|
||||
[permissions]
|
||||
network = "Access the local Job Server and download environment dependencies and model weights"
|
||||
|
||||
@@ -31,6 +31,13 @@ class ServerPanel(bpy.types.Panel):
|
||||
text="Install Environment",
|
||||
icon="IMPORT",
|
||||
)
|
||||
mirror_row = layout.row()
|
||||
mirror_row.enabled = not running
|
||||
mirror_row.operator(
|
||||
"snapform.install_environment_mirror",
|
||||
text="Install Environment (Mirror)",
|
||||
icon="IMPORT",
|
||||
)
|
||||
return
|
||||
|
||||
status = runtime.server_status()
|
||||
|
||||
@@ -42,6 +42,13 @@ class SnapFormPreferences(bpy.types.AddonPreferences):
|
||||
text="Install Environment",
|
||||
icon="IMPORT",
|
||||
)
|
||||
mirror_row = layout.row()
|
||||
mirror_row.enabled = runtime.active_job is None
|
||||
mirror_row.operator(
|
||||
"snapform.install_environment_mirror",
|
||||
text="Install Environment (Mirror)",
|
||||
icon="IMPORT",
|
||||
)
|
||||
layout.separator()
|
||||
layout.operator(
|
||||
"snapform.open_server_log",
|
||||
|
||||
@@ -261,7 +261,7 @@ class AddonTest(unittest.TestCase):
|
||||
self.assertIn("--config", command)
|
||||
self.assertNotIn("--venv-dir", command)
|
||||
self.assertNotIn("--models-dir", command)
|
||||
self.assertEqual(command[command.index("--stages") + 1], "1")
|
||||
self.assertNotIn("--stages", command)
|
||||
|
||||
def test_runtime_exposes_declared_environment_and_post_install(self):
|
||||
runtime = self.addon.runtime
|
||||
@@ -372,12 +372,14 @@ class AddonTest(unittest.TestCase):
|
||||
controller._update_post_progress(
|
||||
{"job_id": "first", "job_type": "download-model", "progress": 1.0}
|
||||
)
|
||||
first_stage = controller.post_status["stage"]
|
||||
self.assertEqual(controller.post_status["job_id"], "first")
|
||||
controller._update_post_progress(
|
||||
{"job_id": "second", "job_type": "download-model", "progress": 0.1}
|
||||
)
|
||||
|
||||
self.assertGreater(controller.post_status["stage"], first_stage)
|
||||
self.assertEqual(controller.post_status["job_id"], "second")
|
||||
self.assertEqual(controller.post_status["progress"], 0.1)
|
||||
self.assertNotIn("stage", controller.post_status)
|
||||
self.assertNotIn("stages", controller.post_status)
|
||||
|
||||
def test_conversion_model_lists_default_to_moge2_vits(self):
|
||||
@@ -442,7 +444,94 @@ class AddonTest(unittest.TestCase):
|
||||
("snapform.download_required_models", True),
|
||||
)
|
||||
self.assertIn(("snapform.convert_to_depth_plane", False), calls)
|
||||
self.assertIn(("snapform.convert_to_surface", True), calls)
|
||||
self.assertIn(("snapform.convert_to_surface", False), calls)
|
||||
self.assertIn(("snapform.remove_image_background", False), calls)
|
||||
|
||||
def test_image_menu_keeps_disabled_features_when_environment_is_missing(self):
|
||||
calls = []
|
||||
|
||||
class Layout:
|
||||
operator_context = ""
|
||||
enabled = True
|
||||
|
||||
def __init__(self, enabled=True):
|
||||
self.enabled = enabled
|
||||
|
||||
def row(self):
|
||||
return Layout(self.enabled)
|
||||
|
||||
def operator(self, identifier, **_options):
|
||||
calls.append((identifier, self.enabled))
|
||||
return SimpleNamespace()
|
||||
|
||||
def separator(self):
|
||||
calls.append(("separator", self.enabled))
|
||||
|
||||
menu = self.addon.SnapFormImageMenu()
|
||||
menu.layout = Layout()
|
||||
with patch.object(
|
||||
self.addon.runtime,
|
||||
"environment_ready",
|
||||
return_value=False,
|
||||
), patch(
|
||||
"snapform.properties.required_models_ready",
|
||||
return_value=False,
|
||||
):
|
||||
menu.draw(None)
|
||||
|
||||
self.assertEqual(calls[0], ("snapform.install_environment", True))
|
||||
self.assertEqual(
|
||||
calls[1],
|
||||
("snapform.install_environment_mirror", True),
|
||||
)
|
||||
self.assertNotIn(("snapform.download_required_models", True), calls)
|
||||
self.assertIn(("snapform.convert_to_depth_plane", False), calls)
|
||||
self.assertIn(("snapform.convert_to_surface", False), calls)
|
||||
self.assertIn(("snapform.remove_image_background", False), calls)
|
||||
|
||||
def test_image_operators_require_environment_and_required_models(self):
|
||||
context = SimpleNamespace(
|
||||
object=SimpleNamespace(data=SimpleNamespace()),
|
||||
)
|
||||
operators = (
|
||||
(self.addon.image_depth, self.addon.ConvertToDepthPlane),
|
||||
(self.addon.image_mesh, self.addon.ConvertToSurface),
|
||||
(self.addon.image_depth, self.addon.RemoveImageBackground),
|
||||
)
|
||||
|
||||
states = (
|
||||
(False, True, False),
|
||||
(True, False, False),
|
||||
(True, True, True),
|
||||
)
|
||||
for environment_ready, models_ready, expected in states:
|
||||
with self.subTest(
|
||||
environment_ready=environment_ready,
|
||||
models_ready=models_ready,
|
||||
expected=expected,
|
||||
), patch.object(
|
||||
self.addon.runtime,
|
||||
"environment_ready",
|
||||
return_value=environment_ready,
|
||||
), patch.object(
|
||||
self.addon.runtime,
|
||||
"server_busy",
|
||||
return_value=False,
|
||||
), patch(
|
||||
"snapform.properties.required_models_ready",
|
||||
return_value=models_ready,
|
||||
):
|
||||
for module, operator in operators:
|
||||
with self.subTest(operator=operator.__name__), patch.object(
|
||||
module,
|
||||
"is_image_empty",
|
||||
return_value=True,
|
||||
), patch.object(
|
||||
module,
|
||||
"video_total_frames",
|
||||
return_value=1,
|
||||
):
|
||||
self.assertEqual(operator.poll(context), expected)
|
||||
|
||||
def test_ui_reads_required_model_files_without_starting_server(self):
|
||||
properties = importlib.import_module("snapform.properties")
|
||||
@@ -490,8 +579,10 @@ class AddonTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_server_request_returns_job_result_and_calls_response(self):
|
||||
blendjob = importlib.import_module("blendjob")
|
||||
controller = blendjob.ServerController.__new__(blendjob.ServerController)
|
||||
controller_type = importlib.import_module(
|
||||
"blendjob.controller"
|
||||
).ServerController
|
||||
controller = controller_type.__new__(controller_type)
|
||||
statuses = iter(
|
||||
(
|
||||
{"state": "running", "progress": 0.5},
|
||||
|
||||
@@ -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.8-py3-none-any.whl",
|
||||
"wheels/blendjob-0.1.13-py3-none-any.whl",
|
||||
names,
|
||||
)
|
||||
self.assertFalse(
|
||||
|
||||
@@ -105,10 +105,10 @@ class ServerTest(unittest.TestCase):
|
||||
def wait_for_job(self, context, timeout=2.0):
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
if context.snapshot()["state"] in {"succeeded", "failed", "cancelled"}:
|
||||
return context.snapshot()
|
||||
if context._snapshot()["state"] in {"succeeded", "failed", "cancelled"}:
|
||||
return context._snapshot()
|
||||
time.sleep(0.01)
|
||||
self.fail(f"Job did not finish: {context.snapshot()}")
|
||||
self.fail(f"Job did not finish: {context._snapshot()}")
|
||||
|
||||
def test_ben2_model_is_reused_for_matching_runtime_key(self):
|
||||
loads = []
|
||||
@@ -267,7 +267,7 @@ class ServerTest(unittest.TestCase):
|
||||
server = self.create_server()
|
||||
context = server.submit("remove-background", {}, job_id="job")
|
||||
self.assertIsNotNone(server.cancel("job"))
|
||||
self.assertTrue(context.is_cancelled())
|
||||
self.assertTrue(context._is_cancelled())
|
||||
status = self.wait_for_job(context)
|
||||
|
||||
self.assertEqual(status["state"], "cancelled")
|
||||
@@ -291,8 +291,8 @@ class ServerTest(unittest.TestCase):
|
||||
|
||||
self.assertRegex(context.job_id, r"^[0-9a-f]{32}$")
|
||||
self.assertEqual(context.directory.name, context.job_id)
|
||||
self.assertEqual(context.snapshot()["state"], "succeeded")
|
||||
self.assertEqual(context.snapshot()["progress"], 1.0)
|
||||
self.assertEqual(context._snapshot()["state"], "succeeded")
|
||||
self.assertEqual(context._snapshot()["progress"], 1.0)
|
||||
|
||||
def test_runtime_storage_root_owns_the_jobs_directory(self):
|
||||
storage_root = self.storage_root
|
||||
@@ -308,7 +308,7 @@ class ServerTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(server.jobs_directory, storage_root / "jobs")
|
||||
self.assertEqual(context.storage_root, storage_root.resolve())
|
||||
self.assertEqual(context.snapshot()["result"], {"value": 42})
|
||||
self.assertEqual(context._snapshot()["result"], {"value": 42})
|
||||
|
||||
def test_job_server_owns_and_clears_shared_resources(self):
|
||||
events = []
|
||||
|
||||
10
uv.lock
generated
10
uv.lock
generated
@@ -8,10 +8,10 @@ resolution-markers = [
|
||||
|
||||
[[package]]
|
||||
name = "blendjob"
|
||||
version = "0.1.8"
|
||||
source = { path = "src/snapform/wheels/blendjob-0.1.8-py3-none-any.whl" }
|
||||
version = "0.1.13"
|
||||
source = { path = "src/snapform/wheels/blendjob-0.1.13-py3-none-any.whl" }
|
||||
wheels = [
|
||||
{ filename = "blendjob-0.1.8-py3-none-any.whl", hash = "sha256:9e94b42dfe0dde62702dff119243d5db3bfd4b4b3f0553191aaada1452b4338d" },
|
||||
{ filename = "blendjob-0.1.13-py3-none-any.whl", hash = "sha256:346aecb6bdd7f63c801c0e7c935fab25c3e3132af195d0f8794975fffa33feda" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
@@ -305,7 +305,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "snapform"
|
||||
version = "0.12.6"
|
||||
version = "0.12.11"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "blendjob" },
|
||||
@@ -320,7 +320,7 @@ dev = [
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "blendjob", path = "src/snapform/wheels/blendjob-0.1.8-py3-none-any.whl" }]
|
||||
requires-dist = [{ name = "blendjob", path = "src/snapform/wheels/blendjob-0.1.13-py3-none-any.whl" }]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
||||
Reference in New Issue
Block a user