# SnapForm `SnapForm` 是一套从图片快速构建空间结构的 Blender Extension。目前以 [Depth Anything 3](https://github.com/ByteDance-Seed/Depth-Anything-3) 作为首个 AI 能力,在独立 Runtime 中运行 DA3,可将官方导出结果保存在独立 job 目录, 也可把 Image Empty 转换成带几何与材质置换的立体面片。Debug 面板还可运行 [BEN2](https://github.com/PramaLLC/BEN2) 背景剔除功能测试。 ## 当前功能 - 使用 Blender Extension manifest,可通过 `Install from Disk` 安装 - 在独立 Python Runtime 的常驻 HTTP AI Host 中运行 Torch、DA3 与 BEN2,避免污染 Blender Python 进程 - Blender 使用内置 `urllib.request` 访问仅监听 `127.0.0.1` 的 Host;请求带随机 Token - 常驻 Host 复用 Torch、CUDA、BEN2 与最近使用的 DA3 模型,连续执行时不再重复加载 - 多个 Blender 实例自动连接同一个共享 Host;`Host` 面板显示状态、端口、客户端与模型缓存,并提供安装、启动、清理、重启和停止控制 - Runtime、模型和任务结果默认保存在 `~/.snapform`,可由多个 Blender 版本共享 - 支持 DA3 Small、Base、Large 1.1、Giant 1.1 和 Nested Giant Large 1.1 - 支持 BEN2 Base 单图背景剔除测试,输出透明 PNG 与推理耗时 - 支持单张 JPG、JPEG、PNG、WEBP 图片或常见视频文件输入 - 视频均匀抽帧,可设置参与重建的最大帧数 - `Host > Models` 默认折叠,展开后可逐项查看和下载所有可用模型 - Runtime 已就绪时不额外占用面板空间;模型列表每个模型只占一行 - `Debug` 默认折叠,内部的 DA3 与 BEN2 测试项也分别默认折叠 - 使用一个导出类型下拉菜单和一个 `Export` 按钮 - 只显示当前格式的专属参数;GLB 与 PLY 设置不会同时出现 - Debug 的模型选项按格式过滤;PLY 只提供两个带 Gaussian head 的 Giant 模型 - Debug 与 Convert to Depth Plane 的模型选项直接显示下载状态;未下载模型带 `(Missing)`,已下载模型保持原名 - 分别调用 DA3 的 `glb`、`gs_ply`、`depth_vis` 和 `mini_npz` exporter - Image Empty 右键菜单可直接运行 DA3,根据 `mini_npz` 深度生成面片 - Image Empty 右键菜单可直接运行 BEN2,保留原图并换用新建的透明背景图片 - 使用浮点深度贴图、Simple Subdivision、Displace Modifier 和原图 Bump 构建立体面片 - 转换对话框可选择推理模式、处理分辨率和面片网格细分 - Debug 的 DA3 导出或 BEN2 背景剔除完成后只打开对应 job 文件夹,不向当前 Blender 文件添加任何数据 - 完整保留每次任务生成的所有官方文件 - 安装和模型下载使用后台子进程;推理提交给常驻 AI Host,状态栏实时显示阶段、百分比进度条与取消按钮 - 后台任务运行时,Install、Download 和 Export 按钮保持原位置并变灰,任务完成后恢复 ## 使用 1. 使用 `uv run python pack.py` 构建 Extension ZIP。 2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。 3. 在 Extension Preferences 中确认 `uv` 路径和 Torch Backend。 4. 在 `Host` 点击 `Install Runtime`。首次安装会下载 Python、PyTorch、DA3、BEN2、FastAPI、Uvicorn, 并默认下载 `DA3 Small`、`DA3 Base` 和 `BEN2 Base` 模型。 5. 在 3D View 按 `N`,打开 `SnapForm` 页签。 6. 如需其他模型,展开 `Host > Models`,找到模型并点击该行的 `Download`。 7. 展开默认收起的 `Debug`,选择 `Export Format` 和本次执行使用的模型。 8. 选择一张图片或一个视频文件。视频可设置均匀抽取的帧数,然后点击 `Export`。 9. 测试背景剔除时,确认 `Host > Models` 中的 `BEN2 Base` 已就绪,然后在 `Debug` 的 `BEN2 Background Removal` 区域选择单张图片并点击 `Remove Background`。 完成后会打开 job 文件夹,其中的 `foreground.png` 带透明 Alpha;状态栏显示 BEN2 推理、模型加载与任务总耗时。当前测试固定关闭较慢的 foreground refinement。 ## Image Empty 去背景 1. 确认 `Host > Models` 中的 `BEN2 Base` 已就绪;首次安装 Runtime 时会默认下载。 2. 在 3D View 选中 Image Empty,右键选择 `Remove Background`。 3. 完成后以 Fake User 保留原 Image datablock,新建并 Pack 一张透明 PNG 到当前 `.blend`,然后只把 当前 Empty 的 `data` 换成新图;Empty 的 Transform、显示尺寸、Offset 及其他设置保持不变。 4. 新 Image 使用原图去掉格式后再追加 `.rb` 的名称。例如 `img.png` 生成 `img.rb`, `img.png.001` 生成 `img.001.rb`。已有同名 datablock 时由 Blender 自动追加编号。 原图已 Pack 时会先写出临时 PNG 交给 BEN2,任务结束后删除临时输入;原 Image 仍保持 Pack 状态和原始内容。BEN2 的 `foreground.png` 也会保留在对应 job 目录中。 ## Image Empty 转深度面片 1. 在 3D View 选中 `Object Type = Empty`、`Display As = Image` 的对象。 2. 右键选择 `Convert to Depth Plane`,通过 `Depth from` 选择深度来源: - `AI Generated`:显示 Model、Use Ray Pose 和 Resolution Scale。实际 Process Res 为图片长边像素数 乘以 Resolution Scale;范围为 `0.1–1.0`,默认 `1.0`。计算后的 Process Res 会直接显示在 `Resolution Scale (… px)` 标签中。 转换导出 `mini_npz`,使用前需要在 `Host > Models` 下载所选模型,并固定使用 `process_res_method="upper_bound_resize"`;设备和参考视图策略 继续使用场景设置。Model 下拉菜单只列出已经下载的 DA3 模型;若没有可用模型,对话框会引导到 `Host > Models` 下载。 - `Original`:直接把原图作为深度贴图,不显示估算参数。Remove Background 关闭时不启动 Runtime,也不要求源图片存在于磁盘。 `Original` 是默认深度来源。选择 `AI Generated` 时,其 Model、Use Ray Pose 与 Resolution Scale 参数会集中显示在独立边框中。两种模式都有默认折叠的 Options,内部依次为 Displace via、 Subdivision、Remove Background 和 Unshaded;Delete Original 保持在折叠项外。 Remove Background 默认关闭,与 Depth from 无关;开启后需要下载 `BEN2 Base`,并从原图生成、 Pack `.rb` 透明图片。它只替换生成面片材质的颜色图片,不修改源 Image Empty 的 Image datablock。 AI Generated 仍从原图生成 `.depth`;例如 `img.png` 会得到 `img.rb` 与 `img.depth`,不会生成 `img.rb.depth`。Original 仍用原图作为位移来源,不改变原有深度逻辑。 Displace via 可选 `Modifier`(默认)或 `Material`; Unshaded 默认关闭;Subdivision 默认为 `5`,并默认删除原对象。 3. AI Generated 完成后会自动创建面片。面片几何先由 `mini_npz` 的相机内参与深度确定,再通过对象 Transform 匹配原 Image Empty 的可见尺寸、位置、旋转和 Offset。Original 模式直接使用源 Empty 的可见尺寸、Offset 与 Transform。开启 `Delete Original` 时,成功生成后 删除源 Empty。生成面片 Object 使用源 Empty 的原名;关闭 `Delete Original` 时不处理 同名冲突,由 Blender 自动为生成面片追加编号。 4. 短边分段数为 `2^Subdivision`,长边分段数根据图像宽高比取最接近的整数。例如高:宽为 `2:3`、Subdivision 为 `1` 时,直接生成 `2×3` 个面;Subdivision 为 `3` 时生成 `8×12` 个面。随后的 Simple Subdivision 修改器使用原生名称,Viewport Levels 固定为 `1`,Render Levels 固定为 `2`,不再随 Resolution Scale 或 Subdivision 动态变化。 所有任务使用统一的状态栏进度显示。变化中的阶段说明显示在右侧的紧凑进度条内部,取消按钮 位于进度条右侧。Image Depth 的阶段包括 Runtime 启动、输入准备、模型加载、深度估算、缓存 写出和面片构建。状态更新仅标记相关区域重绘,不强制执行窗口 Draw/Swap。 导入过程精确保留 NPZ 内深度值之间的差值。例如深度 `2.0` 和 `5.5` 在 `Displacement Strength = -1` 时,沿局部 Z 轴相差 `3.5 Blender Unit`。它不会把数值压缩到 `0..1`,也不再提供视觉归一化模式。 面片尺寸以有效深度的中位数 `Zref` 作为参考截面。先用 `mini_npz` 中的相机内参计算原始 投影面积,再恢复源图纵横比: ```text RawWidth = depth_width × Zref × abs(Displacement Strength) / fx RawHeight = depth_height × Zref × abs(Displacement Strength) / fy Area = RawWidth × RawHeight Aspect = source_image_width / source_image_height Width = sqrt(Area × Aspect) Height = sqrt(Area / Aspect) ``` 这样即使 DA3 估计出的 `fx/fy` 不一致,也不会把正方形原图拉成长方形。主点 `cx/cy` 仍用于 确定原始相机面片,随后对象 XY Transform 将它匹配到源 Image Empty。生成对象会记录 `snapform_reference_depth`、`snapform_camera_plane_width` 和 `snapform_camera_plane_height`,便于检查实际采用的 参考深度和尺寸。 选择 `Modifier` 时,AI Generated 的 Displace Modifier 深度贴图使用 `Extension = Extend`, `Strength` 固定为 `-1`、`Midlevel` 固定为 `1`。Original 把原图指定给 Displace Texture,并设置 `Texture Coordinates = UV`、`Midlevel = 0`;其余修改器设置保留 Blender 默认值。 选择 `Material` 时,不创建 Displace Modifier;深度 Image Texture 的 Color 连接到 Displacement 节点的 Height,再连接 Material Output 的 Displacement,并把材质 Surface Displacement 设置为 `Displacement Only`。AI Generated 使用 `Midlevel = 1`、`Scale = -1`;Original 使用 `Midlevel = 0`、`Scale = 1`,与对应修改器值一致。无论选择哪种呈现方式,原始彩色贴图都会连接 Principled BSDF 的 `Base Color`,并通过 `Bump.Height → Bump.Normal → Principled.Normal` 提供表面细节。Bump 的基础 Distance 为 `0.001`,会乘以面片 Transform 的统一缩放倍率。原图 Color 还会经过 `Float Curve` 连接到 Principled BSDF 的 `Roughness`,曲线两端输出分别为 `0.5` 和 `0.1`;材质 IOR 保持 Principled BSDF 默认值 `1.5`。 开启 `Unshaded` 后,原图 Color 连接 Emission,原图 Alpha 作为 Mix Shader 系数,在 Transparent BSDF 与 Emission 之间混合后连接 Material Output;不创建 Principled BSDF、Bump 或 Float Curve。若 Displace via 选择 `Material`,深度贴图与 Displacement 节点仍会保留并连接 Material Output 的 Displacement。 这里的“原始距离”是 DA3 模型输出的尺度。普通 Small、Base、Large 和 Giant 模型的深度不应 被解释为现实世界的米;需要现实尺度时应选择带 metric depth 的 `DA3 Nested Giant Large 1.1`。此外,当前结果仍是固定 XY 平面上的深度位移面,不是使用 相机内参逐像素反投影得到的完整 camera-space 网格;中位深度截面为现有 Displace Modifier 工作流提供正确的整体高宽尺度。 生成完成后会直接创建面片,不提供缓存重建入口或单独的导入参数。重新选择 `Convert to Depth Plane` 会按当前 Depth from 再次转换。 面片、深度 Image、Texture 与 Material 会在同一个 Blender Undo 步骤中创建;转换完成后执行 `Ctrl+Z` 会完整撤销本次转换,不会越过转换去回退更早的 Image Empty 操作。 开启 Remove Background 时,`.rb` Image 的创建、Pack 和深度面片生成也属于 同一个 Undo 步骤。 Image Empty 使用 Packed Image 时,SnapForm 会通过 Blender 写出临时 PNG 交给 DA3, 不会 Unpack 或修改源 Image;任务结束后自动删除临时文件。普通图片仍直接读取源文件。DA3 的原始 `results.npz` 保留在对应 job 的 `exports/mini_npz/` 中,生成的浮点深度 Image 会 Pack 到 当前 `.blend`。Depth Image 与 Displace Texture 根据源 Image datablock 命名。例如 Empty Object 名为 `obj_img`、引用的 Image 名为 `img.png` 时,Depth Image 与 Texture 都命名为 `img.depth`; 生成的 Object、Mesh 与 Material 仍命名为 `obj_img`。Pack 使用 OpenEXR,因而不会把大于 `1.0` 的原始深度截断。已有同名 datablock 时由 Blender 自动追加 `.001` 等编号。 选择 PLY 后,模型菜单只会提供 `DA3 Giant 1.1` 和 `DA3 Nested Giant Large 1.1`。Small、Base 和 Large 没有 Gaussian head,无法由 DA3 生成 `gs_ply`。 每次任务会在 `~/.snapform/jobs/<任务目录>/` 中保留输出产物: ```text / └── scene.glb / └── gs_ply/*.ply / └── depth_vis/*.jpg / └── exports/mini_npz/results.npz / └── foreground.png ``` 建议从 `DA3 Base` 开始。它使用 Apache 2.0 模型权重;`DA3 Large 1.1` 权重采用 CC BY-NC 4.0,仅限非商业用途。 ## 输出格式说明 `Export Format` 下拉菜单分别把以下原始 `export_format` 传给 DA3: - GLB:`glb` - PLY:`gs_ply`,同时传入 `infer_gs=True` - Depth Images:`depth_vis` - Mini NPZ:`mini_npz` 界面使用 DA3 API 的参数名、选项和默认值。GLB 专属设置仅在选择 `GLB` 时显示, `gs_views_interval` 仅在选择 `PLY (3DGS)` 时显示: - `process_res=504` - `process_res_method="upper_bound_resize"` - `use_ray_pose=False` - `ref_view_strategy="saddle_balanced"` - `conf_thresh_percentile=40.0` - `num_max_points=1_000_000` - `show_cameras=True` - `export_kwargs["gs_ply"]["gs_views_interval"]=1` DA3 官方 `glb` 是彩色点云与可选相机线框;`gs_ply` 是带 Gaussian 属性的 PLY; `depth_vis` 是原图与着色深度图的并排预览;`mini_npz` 保存深度、置信度和可用的相机参数。 GLB 任务会关闭其默认附带的深度预览,使四种 job 产物保持分离。 官方格式与限制可参阅 [DA3 Python API 文档](https://github.com/ByteDance-Seed/Depth-Anything-3/blob/main/docs/API.md)。 ## Runtime Extension 本身不包含 Torch。`Install Runtime` 使用 `uv` 创建独立 Python 3.12 环境,并安装: - PyTorch 2.9.1 - TorchVision 0.24.1 - xFormers 0.0.33.post2(与 PyTorch 2.9.1 对应) - 固定到已验证 commit 的 Depth Anything 3 - 固定到已验证 commit 的 BEN2 - FastAPI 0.139.2 与 Uvicorn 0.51.0 - 固定到 DA3 官方推荐 commit 的 `gsplat`;仅安装 PLY 导出所需的纯 Python 部分, 不编译本机 CUDA rasterizer - `addict` 2.4.0(补充 DA3 上游包元数据暂未声明的运行依赖) 默认 CUDA Backend 为 CUDA 12.8,也可以选择 CPU。CPU 可以用于验证,但实际推理 通常很慢。 Extension 启用后只会查找已有的共享 AI Host,不会主动启动新进程。第一次执行 AI 操作或在侧边栏 手动点击 `Start` 时才会启动 Host。Host 只绑定回环地址,并让操作系统分配动态端口;第一个实例把 端口、进程和随机 Token 写入 `host.json`,其余 Blender 通过标准库 `urllib.request` 连接同一个 Host。`host.lock` 保证同一 Storage Root 同时只有一个 Host,Token 则阻止同机其他进程直接调用接口。Host 全局同时只执行一个 AI 任务。 每个 Blender 会注册独立客户端并每 5 秒发送心跳。正常关闭 Blender 时仅注销自身,不会直接终止 其他 Blender 正在使用的 Host;异常退出的客户端会在 20 秒后过期。最后一个客户端离开后,Host 保留模型缓存等待 60 秒,再在空闲时自动退出。取消任务不会重启 Host,并会保留已经加载的模型; 如果取消发生在一次不可中断的 Torch 调用中,任务会在该调用返回后的最近检查点结束。 侧边栏 `Host` 面板以 `Running (Clients: n)` 表示空闲服务,以 `Running (Busy)` 表示正在执行任务, 并显示缓存模型和活动任务。空闲且只有当前 Blender 连接时,可以清理模型缓存、重启或停止 Host; 多客户端或任务执行期间不会允许破坏共享服务。 BEN2 在首次使用后保持加载;DA3 保留最近使用的一个模型。切换 DA3 模型时会释放旧模型;加载时如果 显存不足,会先释放另一类缓存模型并重试。 Runtime Schema 已升级,旧 Runtime 需要重新执行 `Install Runtime` 才能安装 HTTP Host 依赖。 Host 的启动和崩溃日志写入 `~/.snapform/ai-host.log`。 Runtime、模型和任务结果默认保存在用户主目录的 `~/.snapform`: ```text ~/.snapform/ ├── runtime/ ├── models/ ├── jobs/ ├── ai-host.log ├── host.json # Host 运行时存在 └── host.lock # Host 运行时持有 ``` 不同 Blender 版本安装相同 Extension 后会共用这个目录。也可以在 Extension Preferences 的 `Storage Root` 中修改共享位置。不要把模型写入 Extension 安装目录, 因为该目录可能只读,并且升级时会被替换。不要同时从多个 Blender 实例执行 `Install Runtime`;推理 Host、任务队列和模型读取可以共享。 ## 开发 ```bash uv run python -m unittest discover -s tests -v uv run python pack.py ``` 项目结构: ```text . ├── pack.py ├── src/ │ └── snapform/ │ ├── blender_manifest.toml │ ├── ai_host.py │ ├── bootstrap_runtime.py │ ├── models.py │ ├── image_depth.py │ ├── operators.py │ ├── panel.py │ ├── preferences.py │ ├── properties.py │ ├── runtime.py │ └── worker.py └── tests/ ``` ## 上游与许可证 - Depth Anything 3 代码采用 Apache 2.0。 - BEN2 代码与模型权重采用 MIT License。 - 本 Extension 代码采用 GPL-3.0-or-later。 - 模型权重许可证由所选 Hugging Face 模型仓库决定,Extension 下载前会在界面显示。