chore: Bump version to 0.12.13 and update documentation for color space handling
This commit is contained in:
@@ -64,19 +64,19 @@ Bump 合并;网格本身不写入 custom normal,因此重拓扑降低顶点
|
|||||||
当前此功能仅支持单张图片。
|
当前此功能仅支持单张图片。
|
||||||
|
|
||||||
MoGe-2 ViT-S、ViT-B、ViT-L、DA3 Small、DA3 Base 与 BEN2 全部使用 ONNX。模型执行、相机与深度恢复、
|
MoGe-2 ViT-S、ViT-B、ViT-L、DA3 Small、DA3 Base 与 BEN2 全部使用 ONNX。模型执行、相机与深度恢复、
|
||||||
点云重投影和网格生成均不依赖 PyTorch。Windows 使用 DirectML 加速,provider 不可用时会回退到 ONNX Runtime CPU,
|
点云重投影和网格生成均不依赖 PyTorch。Windows 使用 DirectML 加速,macOS 使用 CoreML 的 `MLProgram` 模型格式;provider 不可用时会回退到 ONNX Runtime CPU,
|
||||||
并在任务状态中显示实际 provider。
|
并在任务状态中显示实际 provider。
|
||||||
|
|
||||||
转换对话框提供模型与 `Create Matching Camera`、`Remove Background`、`Shadeless`;DA3 显示
|
转换对话框提供模型与 `Create Matching Camera`、`Remove Background`、`Shadeless`;DA3 显示
|
||||||
`Resolution Scale`,MoGe-2 显示 `Resolution Level`,
|
`Resolution Scale`,MoGe-2 显示 `Resolution Level`,
|
||||||
以及分割线下方的 `Keep Original`。
|
以及分割线下方的 `Keep Original`。
|
||||||
界面会显示计算所得的实际 Process Res。剔除和重拓扑参数直接在
|
界面会显示计算所得的实际 Process Res。剔除和重拓扑参数直接在
|
||||||
Geometry Nodes 修改器中调整;`Shadeless` 与 Depth Plane 共用 `O Shadeless` 材质节点组。颜色贴图始终使用统一的 `.color` 转换产物;Remove Background 只决定是否由 BEN2 修改 Alpha,并使用 `AgX Base sRGB`。普通材质的
|
Geometry Nodes 修改器中调整;`Shadeless` 与 Depth Plane 共用 `O Shadeless` 材质节点组。颜色贴图始终使用统一的 `.color` 转换产物;Remove Background 只决定是否由 BEN2 修改 Alpha。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `AgX Base sRGB`;当前 Blender 连该选项也不支持时才安全回退 `sRGB`。普通材质的
|
||||||
Bump Scale 会在材质组内乘以 `0.001`,并按透视表面的深度空间缩放。Surface 与 Depth Plane 的
|
Bump Scale 会在材质组内乘以 `0.001`,并按透视表面的深度空间缩放。Surface 与 Depth Plane 的
|
||||||
Layer Color 同时驱动 Principled BSDF 的 Base Color、Subsurface Radius 和 Emission Color,Layer Normal
|
Layer Color 同时驱动 Principled BSDF 的 Base Color、Subsurface Radius 和 Emission Color,Layer Normal
|
||||||
同时驱动 Normal 与 Coat Normal;Subsurface、Emission、Coat 的其他设置保持默认。Surface attributes
|
同时驱动 Normal 与 Coat Normal;Subsurface、Emission、Coat 的其他设置保持默认。Surface attributes
|
||||||
统一使用 `o_` 前缀;Server 准备过程显示
|
统一使用 `o_` 前缀;Server 准备过程显示
|
||||||
`Preparing surface data n/6`,可看到当前阶段与总步骤;Surface 单帧模型推理不显示多余的 `1/1`。
|
`Preparing surface data n/6`,可看到当前阶段与总步骤;Surface 与 Depth 单帧模型推理都不显示多余的 `1/1`。
|
||||||
|
|
||||||
### 视频与序列帧
|
### 视频与序列帧
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
`Keep Original` 默认关闭,并在分割线下方保持为最后一项。仅当开启 `Remove Background` 时才需要
|
`Keep Original` 默认关闭,并在分割线下方保持为最后一项。仅当开启 `Remove Background` 时才需要
|
||||||
`BEN2 Base`,并把 `.color` 的 Alpha 作为独立 Mask,不在 Server 中改写深度值。无论颜色层是直接
|
`BEN2 Base`,并把 `.color` 的 Alpha 作为独立 Mask,不在 Server 中改写深度值。无论颜色层是直接
|
||||||
复制原图,还是由 BEN2 为单图或序列修改 Alpha,Server 都统一写为 `color.0001.*`;Blender 只导入
|
复制原图,还是由 BEN2 为单图或序列修改 Alpha,Server 都统一写为 `color.0001.*`;Blender 只导入
|
||||||
这一份 `.color`,不复用源 Empty 的 Image,Color Space 固定为 `AgX Base sRGB`。
|
这一份 `.color`,不复用源 Empty 的 Image。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `AgX Base sRGB`;当前 Blender 连该选项也不支持时才安全回退 `sRGB`。
|
||||||
Image Empty 引用视频或序列帧时,DA3 与 MoGe-2 会按 Empty 的 Image Frames 数值逐帧估计
|
Image Empty 引用视频或序列帧时,DA3 与 MoGe-2 会按 Empty 的 Image Frames 数值逐帧估计
|
||||||
深度(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset,与背景蒙版范围一致;
|
深度(未设置时为完整帧数,显式设为 1 时只处理 1 帧),起始帧跟随 Empty 的 Offset,与背景蒙版范围一致;
|
||||||
DA3 会复用同一个 ONNX Session 对每帧独立推理,再合并为原有序列输出,避免多帧 Attention 的平方级显存占用。
|
DA3 会复用同一个 ONNX Session 对每帧独立推理,再合并为原有序列输出,避免多帧 Attention 的平方级显存占用。
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
一致;Original 模式的视频或序列帧使用 `color.0001.*` 颜色层同时作为颜色与深度输入;开启
|
一致;Original 模式的视频或序列帧使用 `color.0001.*` 颜色层同时作为颜色与深度输入;开启
|
||||||
Remove Background 时同名颜色层包含 BEN2 修改后的 Alpha。
|
Remove Background 时同名颜色层包含 BEN2 修改后的 Alpha。
|
||||||
DA3 的深度仍以 EXR 序列帧(`depth.0001.exr` 起)保存在 job 目录根下;MoGe-2 分别使用 `moge_depth`、`moge_mask` 与 `moge_normal` EXR 序列。
|
DA3 的深度仍以 EXR 序列帧(`depth.0001.exr` 起)保存在 job 目录根下;MoGe-2 分别使用 `moge_depth`、`moge_mask` 与 `moge_normal` EXR 序列。
|
||||||
`Shadeless`、`Remove Background` 与 `Keep Original` 均默认关闭,因此默认删除原对象。`Shadeless` 会以 `O Shadeless` 节点组替代普通 Principled BSDF 路径,并继续通过 Alpha 与 Transparent BSDF 混合。
|
DA3 与 MoGe-2 的单帧推理进度只显示模型与阶段名称,不追加 `1/1`;多帧时继续显示当前帧与总数。`Shadeless`、`Remove Background` 与 `Keep Original` 均默认关闭,因此默认删除原对象。`Shadeless` 会以 `O Shadeless` 节点组替代普通 Principled BSDF 路径,并继续通过 Alpha 与 Transparent BSDF 混合。
|
||||||
3. DA3 或 MoGe-2 完成后会自动创建面片。DA3 面片几何先由 `mini_npz` 的相机内参与深度确定,再通过对象
|
3. DA3 或 MoGe-2 完成后会自动创建面片。DA3 面片几何先由 `mini_npz` 的相机内参与深度确定,再通过对象
|
||||||
Transform 匹配原 Image Empty 的可见尺寸、位置、旋转和 Offset。Original 模式直接使用源 Empty
|
Transform 匹配原 Image Empty 的可见尺寸、位置、旋转和 Offset。Original 模式直接使用源 Empty
|
||||||
的可见尺寸、Offset 与 Transform。DA3 会把用于匹配画面尺寸的缩放预乘到 `.depth` 数值,
|
的可见尺寸、Offset 与 Transform。DA3 会把用于匹配画面尺寸的缩放预乘到 `.depth` 数值,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ uv run pytest tests/test_addon.py tests/test_manifest.py tests/test_pack.py -v
|
|||||||
uv run python pack.py
|
uv run python pack.py
|
||||||
```
|
```
|
||||||
|
|
||||||
`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.11.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试:
|
`pack.py` 根据 `blender_manifest.toml` 分别生成 Windows、macOS 和 Linux 扩展包,例如 `dist/SnapForm.v0.12.13.windows-x64.zip`。更换 `src/snapform/wheels/` 中随扩展分发的 wheel 后,先刷新锁文件再测试:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv lock --refresh
|
uv lock --refresh
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
## 使用
|
## 使用
|
||||||
|
|
||||||
1. 运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、
|
1. 运行 `uv run python pack.py`,分别构建 `windows-x64`、`macos-arm64`、
|
||||||
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.11.macos-arm64.zip`)。
|
`linux-x64` 三个平台的 Extension ZIP(如 `SnapForm.v0.12.13.macos-arm64.zip`)。
|
||||||
安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,并校验固定 SHA-256。官方安装按钮使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI。
|
安装包不携带 uv 或 AI 项目源码;首次安装 Environment 时下载固定版本 uv,并校验固定 SHA-256。官方安装按钮使用 GitHub、uv 默认 Python 下载源和 PyPI;镜像按钮使用 CNB uv Release、CNB Python 解释器镜像和清华 PyPI。
|
||||||
2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。
|
2. 在 Blender 的 `Preferences > Extensions` 中选择 `Install from Disk`。
|
||||||
3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能
|
3. 在 Extension Preferences 中确认 Storage Root 与 Device。Device 是正式功能
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ ONNX 实现位于 `models/`;纯投影、网格与唯一 Surface Builder 位于
|
|||||||
|
|
||||||
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
|
运行环境不安装 PyTorch、TorchVision、xFormers,也不安装 DA3、MoGe、BEN2 或 utils3d 的 Python 源码包。模型推理和 SnapForm 所需的相机、点云、网格后处理均由 ONNX Runtime、NumPy 与内部实现完成。
|
||||||
|
|
||||||
Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。
|
Windows 当前发行环境提供 DirectML 与 CPU;`Auto` 优先使用 `DmlExecutionProvider`。显式指定不可用设备时会安全回退到 CPU。其他平台安装标准 ONNX Runtime,并按可用 provider 选择 CoreML、OpenVINO 或 CPU。macOS 的 CoreML Session 使用 `MLProgram` 模型格式与全部可用计算单元,允许动态输入 Shape;未被 CoreML 接管的节点继续由 CPU 执行。
|
||||||
|
|
||||||
`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
|
`environment.json` 保存规范化后的声明,`manifest.json` 保存该声明与基础 Server packages 的 Hash。Python、packages、平台 packages、基础 Server packages 或 Environment Schema 改变后需要重新执行 `Install Environment`,不再维护手工 Manifest Revision。BlendJob 直接随 Extension 更新,不参与 Environment Hash。
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
6. Server 在 job 根目录写出 `mesh.npz`。NPZ 保存 camera-space `vertices`、`faces`、`uv`、
|
6. Server 在 job 根目录写出 `mesh.npz`。NPZ 保存 camera-space `vertices`、`faces`、`uv`、
|
||||||
`intrinsics`、`image_size` 与有效深度中位数 `reference_depth`。颜色层无论来自原图复制还是 BEN2,
|
`intrinsics`、`image_size` 与有效深度中位数 `reference_depth`。颜色层无论来自原图复制还是 BEN2,
|
||||||
Server 都统一写为 `color.0001.*`。Blender 读取网格数据后额外导入为 `.color`,材质不复用源
|
Server 都统一写为 `color.0001.*`。Blender 读取网格数据后额外导入为 `.color`,材质不复用源
|
||||||
Empty 的 Image,Color Space 固定为 `AgX Base sRGB`。Server 不生成 `texture.png`,材质只在
|
Empty 的 Image。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `AgX Base sRGB`;当前 Blender 连该选项也不支持时才安全回退 `sRGB`。 Server 不生成 `texture.png`,材质只在
|
||||||
Blender 端创建。
|
Blender 端创建。
|
||||||
DA3 Surface 只保留 Geometry Nodes 后处理实际使用的 attributes:点域 `o_depth`、面域
|
DA3 Surface 只保留 Geometry Nodes 后处理实际使用的 attributes:点域 `o_depth`、面域
|
||||||
`o_confidence` 与面域 `o_depth_discontinuity`。Remove Background 已在 Server
|
`o_confidence` 与面域 `o_depth_discontinuity`。Remove Background 已在 Server
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
`Remove Background`、`Shadeless`,以及分割线下方的 `Keep Original`。背景移除默认关闭;开启后 Server
|
`Remove Background`、`Shadeless`,以及分割线下方的 `Keep Original`。背景移除默认关闭;开启后 Server
|
||||||
使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。
|
使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。
|
||||||
该预删除同时适用于 DA3 与 MoGe-2;因此 MoGe-2 转换后即使关闭 `Mask`,BEN2 已删除的背景也不会恢复。
|
该预删除同时适用于 DA3 与 MoGe-2;因此 MoGe-2 转换后即使关闭 `Mask`,BEN2 已删除的背景也不会恢复。
|
||||||
Surface 材质的 Color Image Texture 始终使用 `.color`;Remove Background 只决定该层是否带 BEN2 Alpha,原 Empty 始终保持原图。颜色层使用 `AgX Base sRGB`。
|
Surface 材质的 Color Image Texture 始终使用 `.color`;Remove Background 只决定该层是否带 BEN2 Alpha,原 Empty 始终保持原图。颜色贴图会按当前场景的 View Transform 选择对应输入色彩空间:AgX、ACES 1.3、ACES 2.0、Filmic、Khronos PBR Neutral 分别使用 `AgX Base sRGB`、`ACES 1.3 sRGB`、`ACES 2.0 sRGB`、`Filmic sRGB`、`Khronos PBR Neutral sRGB`;无法匹配当前 View Transform 时回退 `AgX Base sRGB`;当前 Blender 连该选项也不支持时才安全回退 `sRGB`。
|
||||||
`Shadeless` 使用与 Depth Plane 相同的 `O Shadeless` 节点组,并继续通过 Alpha 与 Transparent BSDF 混合;普通材质的
|
`Shadeless` 使用与 Depth Plane 相同的 `O Shadeless` 节点组,并继续通过 Alpha 与 Transparent BSDF 混合;普通材质的
|
||||||
Bump Scale 按 `abs(object.scale.z) × camera scale_z` 设置,并在材质组内乘以固定基准 `0.001`,
|
Bump Scale 按 `abs(object.scale.z) × camera scale_z` 设置,并在材质组内乘以固定基准 `0.001`,
|
||||||
与透视表面实际深度尺度一致;它允许负数以反转凹凸方向。
|
与透视表面实际深度尺度一致;它允许负数以反转凹凸方向。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "snapform"
|
name = "snapform"
|
||||||
version = "0.12.12"
|
version = "0.12.13"
|
||||||
description = "Fast image-guided structure building tools for Blender"
|
description = "Fast image-guided structure building tools for Blender"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -24,7 +24,13 @@ MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME = "O MoGe2 Surface Layer"
|
|||||||
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME = "O DA3 Depth Plane Layer"
|
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME = "O DA3 Depth Plane Layer"
|
||||||
MOGE_MATERIAL_NODE_GROUP_NAME = "O MoGe2 Depth Plane Layer"
|
MOGE_MATERIAL_NODE_GROUP_NAME = "O MoGe2 Depth Plane Layer"
|
||||||
SHADELESS_NODE_GROUP_NAME = "O Shadeless"
|
SHADELESS_NODE_GROUP_NAME = "O Shadeless"
|
||||||
MATERIAL_COLOR_SPACE = "AgX Base sRGB"
|
VIEW_TRANSFORM_COLOR_SPACES = (
|
||||||
|
("ACES 1.3", "ACES 1.3 sRGB"),
|
||||||
|
("ACES 2.0", "ACES 2.0 sRGB"),
|
||||||
|
("AgX", "AgX Base sRGB"),
|
||||||
|
("Filmic", "Filmic sRGB"),
|
||||||
|
("Khronos PBR Neutral", "Khronos PBR Neutral sRGB"),
|
||||||
|
)
|
||||||
DA3_PATCH_SIZE = 14
|
DA3_PATCH_SIZE = 14
|
||||||
DA3_MIN_PROCESS_RES = 252
|
DA3_MIN_PROCESS_RES = 252
|
||||||
DA3_MAX_PROCESS_RES = 1008
|
DA3_MAX_PROCESS_RES = 1008
|
||||||
@@ -70,6 +76,36 @@ def is_image_empty(candidate):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def material_color_space_name(scene):
|
||||||
|
view_settings = getattr(scene, "view_settings", None)
|
||||||
|
view_transform = getattr(view_settings, "view_transform", "")
|
||||||
|
normalized = str(view_transform).strip().casefold()
|
||||||
|
for view_name, color_space in VIEW_TRANSFORM_COLOR_SPACES:
|
||||||
|
if normalized.startswith(view_name.casefold()):
|
||||||
|
return color_space
|
||||||
|
return "AgX Base sRGB"
|
||||||
|
|
||||||
|
|
||||||
|
def set_material_color_space(image, scene=None):
|
||||||
|
if scene is None:
|
||||||
|
scene = getattr(bpy.context, "scene", None)
|
||||||
|
settings = image.colorspace_settings
|
||||||
|
preferred = material_color_space_name(scene)
|
||||||
|
original = settings.name
|
||||||
|
for color_space in dict.fromkeys(
|
||||||
|
(preferred, "AgX Base sRGB", "sRGB", original)
|
||||||
|
):
|
||||||
|
if not color_space:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
settings.name = color_space
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if settings.name == color_space:
|
||||||
|
return color_space
|
||||||
|
return settings.name
|
||||||
|
|
||||||
|
|
||||||
def is_animated_image(image):
|
def is_animated_image(image):
|
||||||
return getattr(image, "source", None) in {"MOVIE", "SEQUENCE"}
|
return getattr(image, "source", None) in {"MOVIE", "SEQUENCE"}
|
||||||
|
|
||||||
@@ -978,6 +1014,7 @@ def create_moge_material(
|
|||||||
frame_count=1,
|
frame_count=1,
|
||||||
frame_start=1,
|
frame_start=1,
|
||||||
use_cyclic=False,
|
use_cyclic=False,
|
||||||
|
scene=None,
|
||||||
):
|
):
|
||||||
material = bpy.data.materials.new(name)
|
material = bpy.data.materials.new(name)
|
||||||
material.use_nodes = True
|
material.use_nodes = True
|
||||||
@@ -990,7 +1027,7 @@ def create_moge_material(
|
|||||||
uv.uv_map = "UVMap"
|
uv.uv_map = "UVMap"
|
||||||
uv.location = (-900.0, 200.0)
|
uv.location = (-900.0, 200.0)
|
||||||
color = nodes.new("ShaderNodeTexImage")
|
color = nodes.new("ShaderNodeTexImage")
|
||||||
color_image.colorspace_settings.name = MATERIAL_COLOR_SPACE
|
set_material_color_space(color_image, scene)
|
||||||
color.image = color_image
|
color.image = color_image
|
||||||
color.interpolation = "Linear"
|
color.interpolation = "Linear"
|
||||||
color.extension = "EXTEND"
|
color.extension = "EXTEND"
|
||||||
@@ -1303,6 +1340,7 @@ def create_material(
|
|||||||
use_cyclic=None,
|
use_cyclic=None,
|
||||||
surface_group_name=None,
|
surface_group_name=None,
|
||||||
normal_image=None,
|
normal_image=None,
|
||||||
|
scene=None,
|
||||||
):
|
):
|
||||||
material = bpy.data.materials.new(name)
|
material = bpy.data.materials.new(name)
|
||||||
material.use_nodes = True
|
material.use_nodes = True
|
||||||
@@ -1318,7 +1356,7 @@ def create_material(
|
|||||||
uv_map.uv_map = "UVMap"
|
uv_map.uv_map = "UVMap"
|
||||||
uv_map.location = (-1250.0, 350.0)
|
uv_map.location = (-1250.0, 350.0)
|
||||||
material_image = color_image
|
material_image = color_image
|
||||||
material_image.colorspace_settings.name = MATERIAL_COLOR_SPACE
|
set_material_color_space(material_image, scene)
|
||||||
color_texture.image = material_image
|
color_texture.image = material_image
|
||||||
color_texture.interpolation = "Linear"
|
color_texture.interpolation = "Linear"
|
||||||
color_texture.extension = "EXTEND"
|
color_texture.extension = "EXTEND"
|
||||||
@@ -1730,6 +1768,7 @@ def build_image_depth_plane(
|
|||||||
depth_frame_offset=texture_frame_offset,
|
depth_frame_offset=texture_frame_offset,
|
||||||
depth_frame_duration=texture_frame_count,
|
depth_frame_duration=texture_frame_count,
|
||||||
use_cyclic=source_use_cyclic,
|
use_cyclic=source_use_cyclic,
|
||||||
|
scene=context.scene,
|
||||||
)
|
)
|
||||||
mesh.materials.append(material)
|
mesh.materials.append(material)
|
||||||
add_depth_plane_processing(
|
add_depth_plane_processing(
|
||||||
@@ -1834,6 +1873,7 @@ def build_depth_plane(
|
|||||||
depth_frame_offset=0,
|
depth_frame_offset=0,
|
||||||
depth_frame_duration=frame_count,
|
depth_frame_duration=frame_count,
|
||||||
use_cyclic=source_use_cyclic,
|
use_cyclic=source_use_cyclic,
|
||||||
|
scene=context.scene,
|
||||||
)
|
)
|
||||||
mesh.materials.append(material)
|
mesh.materials.append(material)
|
||||||
add_depth_plane_processing(
|
add_depth_plane_processing(
|
||||||
@@ -1939,6 +1979,7 @@ def build_moge2_depth_plane(
|
|||||||
frame_count=len(depth),
|
frame_count=len(depth),
|
||||||
frame_start=source_frame_start,
|
frame_start=source_frame_start,
|
||||||
use_cyclic=source_use_cyclic,
|
use_cyclic=source_use_cyclic,
|
||||||
|
scene=context.scene,
|
||||||
)
|
)
|
||||||
mesh.materials.append(material)
|
mesh.materials.append(material)
|
||||||
modifier = add_moge_depth_plane_processing(
|
modifier = add_moge_depth_plane_processing(
|
||||||
|
|||||||
@@ -456,6 +456,7 @@ def build_image_mesh(
|
|||||||
MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME if moge2 else None
|
MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME if moge2 else None
|
||||||
),
|
),
|
||||||
normal_image=normal_image,
|
normal_image=normal_image,
|
||||||
|
scene=context.scene,
|
||||||
)
|
)
|
||||||
mesh.materials.append(material)
|
mesh.materials.append(material)
|
||||||
mesh_object["snapform_reference_depth"] = reference_depth
|
mesh_object["snapform_reference_depth"] = reference_depth
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "snapform"
|
id = "snapform"
|
||||||
version = "0.12.12"
|
version = "0.12.13"
|
||||||
name = "SnapForm"
|
name = "SnapForm"
|
||||||
tagline = "Build spatial structures from images, fast"
|
tagline = "Build spatial structures from images, fast"
|
||||||
maintainer = "OmooLab"
|
maintainer = "OmooLab"
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ from contextlib import contextmanager
|
|||||||
from ..media import prepare_input
|
from ..media import prepare_input
|
||||||
|
|
||||||
|
|
||||||
|
def frame_progress_message(message, index, total):
|
||||||
|
if total > 1:
|
||||||
|
return f"{message} ({index + 1}/{total})"
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def input_frames(context, parameters):
|
def input_frames(context, parameters):
|
||||||
images, temporary_directory = prepare_input(
|
images, temporary_directory = prepare_input(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from ..media import copy_color_images
|
from ..media import copy_color_images
|
||||||
from ..models import da3, geometry_adapter, moge2
|
from ..models import da3, geometry_adapter, moge2
|
||||||
from ..outputs.depth import write_da3_depth, write_moge_depth
|
from ..outputs.depth import write_da3_depth, write_moge_depth
|
||||||
from .common import input_frames
|
from .common import frame_progress_message, input_frames
|
||||||
from .foreground import write_optional
|
from .foreground import write_optional
|
||||||
|
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ def run(context, parameters):
|
|||||||
images,
|
images,
|
||||||
progress_callback=lambda index, total: context.progress(
|
progress_callback=lambda index, total: context.progress(
|
||||||
0.15 + 0.65 * index / total,
|
0.15 + 0.65 * index / total,
|
||||||
f"Estimating DA3 depth ({index + 1}/{total})",
|
frame_progress_message("Estimating DA3 depth", index, total),
|
||||||
),
|
),
|
||||||
cancel_check=context.check_cancelled,
|
cancel_check=context.check_cancelled,
|
||||||
)
|
)
|
||||||
@@ -31,7 +31,7 @@ def run(context, parameters):
|
|||||||
include_points=False,
|
include_points=False,
|
||||||
progress_callback=lambda index, total: context.progress(
|
progress_callback=lambda index, total: context.progress(
|
||||||
0.15 + 0.65 * index / total,
|
0.15 + 0.65 * index / total,
|
||||||
f"Estimating MoGe-2 depth ({index + 1}/{total})",
|
frame_progress_message("Estimating MoGe-2 depth", index, total),
|
||||||
),
|
),
|
||||||
cancel_check=context.check_cancelled,
|
cancel_check=context.check_cancelled,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,17 +2,9 @@ from ..geometry import build_surface, foreground_mesh_mask
|
|||||||
from ..media import copy_color_images
|
from ..media import copy_color_images
|
||||||
from ..models import geometry_adapter, moge2
|
from ..models import geometry_adapter, moge2
|
||||||
from ..outputs.surface import write_normal_texture, write_surface_archive
|
from ..outputs.surface import write_normal_texture, write_surface_archive
|
||||||
from .common import input_frames
|
from .common import frame_progress_message, input_frames
|
||||||
from .foreground import write_optional
|
from .foreground import write_optional
|
||||||
|
|
||||||
|
|
||||||
def inference_progress_message(model_name, index, total):
|
|
||||||
message = f"Estimating {model_name} geometry"
|
|
||||||
if total > 1:
|
|
||||||
message += f" ({index + 1}/{total})"
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
def run(context, parameters):
|
def run(context, parameters):
|
||||||
context.progress(0.02, "Preparing surface input")
|
context.progress(0.02, "Preparing surface input")
|
||||||
context.directory.mkdir(parents=True, exist_ok=True)
|
context.directory.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -30,7 +22,7 @@ def run(context, parameters):
|
|||||||
include_points=True,
|
include_points=True,
|
||||||
progress_callback=lambda index, total: context.progress(
|
progress_callback=lambda index, total: context.progress(
|
||||||
0.12 + 0.58 * index / total,
|
0.12 + 0.58 * index / total,
|
||||||
inference_progress_message("MoGe-2", index, total),
|
frame_progress_message("Estimating MoGe-2 geometry", index, total),
|
||||||
),
|
),
|
||||||
cancel_check=context.check_cancelled,
|
cancel_check=context.check_cancelled,
|
||||||
)
|
)
|
||||||
@@ -41,7 +33,7 @@ def run(context, parameters):
|
|||||||
images,
|
images,
|
||||||
progress_callback=lambda index, total: context.progress(
|
progress_callback=lambda index, total: context.progress(
|
||||||
0.12 + 0.58 * index / total,
|
0.12 + 0.58 * index / total,
|
||||||
inference_progress_message("DA3", index, total),
|
frame_progress_message("Estimating DA3 geometry", index, total),
|
||||||
),
|
),
|
||||||
cancel_check=context.check_cancelled,
|
cancel_check=context.check_cancelled,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
"""Shared ONNX Runtime session helpers."""
|
"""Shared ONNX Runtime session helpers."""
|
||||||
|
|
||||||
|
COREML_PROVIDER_OPTIONS = {
|
||||||
|
"ModelFormat": "MLProgram",
|
||||||
|
"MLComputeUnits": "ALL",
|
||||||
|
"RequireStaticInputShapes": "0",
|
||||||
|
"EnableOnSubgraphs": "0",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def select_providers(requested, available):
|
def select_providers(requested, available):
|
||||||
available = set(available)
|
available = set(available)
|
||||||
@@ -40,6 +47,12 @@ def create_session(path, requested_device="auto"):
|
|||||||
requested_device,
|
requested_device,
|
||||||
onnxruntime.get_available_providers(),
|
onnxruntime.get_available_providers(),
|
||||||
)
|
)
|
||||||
|
providers = [
|
||||||
|
(provider, dict(COREML_PROVIDER_OPTIONS))
|
||||||
|
if provider == "CoreMLExecutionProvider"
|
||||||
|
else provider
|
||||||
|
for provider in providers
|
||||||
|
]
|
||||||
return onnxruntime.InferenceSession(str(path), providers=providers)
|
return onnxruntime.InferenceSession(str(path), providers=providers)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,52 @@ class ImageDepthTest(unittest.TestCase):
|
|||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.image_depth = load_image_depth()
|
cls.image_depth = load_image_depth()
|
||||||
|
|
||||||
|
def test_material_color_space_matches_scene_view_transform(self):
|
||||||
|
mappings = {
|
||||||
|
"ACES 1.3": "ACES 1.3 sRGB",
|
||||||
|
"ACES 2.0": "ACES 2.0 sRGB",
|
||||||
|
"AgX": "AgX Base sRGB",
|
||||||
|
"Filmic": "Filmic sRGB",
|
||||||
|
"Khronos PBR Neutral": "Khronos PBR Neutral sRGB",
|
||||||
|
"Standard": "AgX Base sRGB",
|
||||||
|
}
|
||||||
|
for view_transform, expected in mappings.items():
|
||||||
|
with self.subTest(view_transform=view_transform):
|
||||||
|
scene = SimpleNamespace(
|
||||||
|
view_settings=SimpleNamespace(
|
||||||
|
view_transform=view_transform,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.image_depth.material_color_space_name(scene),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_material_color_space_falls_back_when_unsupported(self):
|
||||||
|
class ColorSpaceSettings:
|
||||||
|
def __init__(self):
|
||||||
|
self._name = "sRGB"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@name.setter
|
||||||
|
def name(self, value):
|
||||||
|
if value != "sRGB":
|
||||||
|
raise TypeError("unsupported color space")
|
||||||
|
self._name = value
|
||||||
|
|
||||||
|
image = SimpleNamespace(colorspace_settings=ColorSpaceSettings())
|
||||||
|
scene = SimpleNamespace(
|
||||||
|
view_settings=SimpleNamespace(view_transform="ACES 2.0")
|
||||||
|
)
|
||||||
|
|
||||||
|
result = self.image_depth.set_material_color_space(image, scene)
|
||||||
|
|
||||||
|
self.assertEqual(result, "sRGB")
|
||||||
|
self.assertEqual(image.colorspace_settings.name, "sRGB")
|
||||||
|
|
||||||
def test_extracts_depth_and_uses_first_view(self):
|
def test_extracts_depth_and_uses_first_view(self):
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
npz_path = Path(directory) / "results.npz"
|
npz_path = Path(directory) / "results.npz"
|
||||||
|
|||||||
@@ -76,6 +76,34 @@ class MogeOnnxTest(unittest.TestCase):
|
|||||||
["DmlExecutionProvider", "CPUExecutionProvider"],
|
["DmlExecutionProvider", "CPUExecutionProvider"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_coreml_session_uses_mlprogram(self):
|
||||||
|
calls = []
|
||||||
|
fake_runtime = ModuleType("onnxruntime")
|
||||||
|
fake_runtime.get_available_providers = lambda: [
|
||||||
|
"CoreMLExecutionProvider",
|
||||||
|
"CPUExecutionProvider",
|
||||||
|
]
|
||||||
|
fake_runtime.InferenceSession = lambda path, providers: calls.append(
|
||||||
|
(path, providers)
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.dict(sys.modules, {"onnxruntime": fake_runtime}):
|
||||||
|
self.moge.create_runtime_session("model.onnx")
|
||||||
|
|
||||||
|
self.assertEqual(calls[0][0], "model.onnx")
|
||||||
|
self.assertEqual(
|
||||||
|
calls[0][1][0],
|
||||||
|
(
|
||||||
|
"CoreMLExecutionProvider",
|
||||||
|
{
|
||||||
|
"ModelFormat": "MLProgram",
|
||||||
|
"MLComputeUnits": "ALL",
|
||||||
|
"RequireStaticInputShapes": "0",
|
||||||
|
"EnableOnSubgraphs": "0",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_explicit_unavailable_provider_falls_back_to_cpu(self):
|
def test_explicit_unavailable_provider_falls_back_to_cpu(self):
|
||||||
providers = self.moge.select_providers(
|
providers = self.moge.select_providers(
|
||||||
"cuda",
|
"cuda",
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class WorkerTest(unittest.TestCase):
|
|||||||
cls.surface_outputs = load_server_module("outputs.surface")
|
cls.surface_outputs = load_server_module("outputs.surface")
|
||||||
cls.debug_da3_outputs = load_server_module("outputs.debug_da3")
|
cls.debug_da3_outputs = load_server_module("outputs.debug_da3")
|
||||||
cls.debug_moge2_outputs = load_server_module("outputs.debug_moge2")
|
cls.debug_moge2_outputs = load_server_module("outputs.debug_moge2")
|
||||||
|
cls.common_job = load_server_module("jobs.common")
|
||||||
cls.depth_job = load_server_module("jobs.depth")
|
cls.depth_job = load_server_module("jobs.depth")
|
||||||
cls.surface_job = load_server_module("jobs.surface")
|
cls.surface_job = load_server_module("jobs.surface")
|
||||||
cls.debug_da3_job = load_server_module("jobs.debug_da3")
|
cls.debug_da3_job = load_server_module("jobs.debug_da3")
|
||||||
@@ -127,20 +128,39 @@ class WorkerTest(unittest.TestCase):
|
|||||||
cls.foreground_job = load_server_module("jobs.foreground")
|
cls.foreground_job = load_server_module("jobs.foreground")
|
||||||
cls.download = load_server_module("model_download")
|
cls.download = load_server_module("model_download")
|
||||||
|
|
||||||
def test_surface_inference_progress_omits_single_frame_count(self):
|
def test_inference_progress_omits_single_frame_count(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.surface_job.inference_progress_message("MoGe-2", 0, 1),
|
self.common_job.frame_progress_message(
|
||||||
|
"Estimating MoGe-2 geometry", 0, 1
|
||||||
|
),
|
||||||
"Estimating MoGe-2 geometry",
|
"Estimating MoGe-2 geometry",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.surface_job.inference_progress_message("DA3", 0, 1),
|
self.common_job.frame_progress_message(
|
||||||
|
"Estimating DA3 geometry", 0, 1
|
||||||
|
),
|
||||||
"Estimating DA3 geometry",
|
"Estimating DA3 geometry",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.surface_job.inference_progress_message("DA3", 1, 3),
|
self.common_job.frame_progress_message(
|
||||||
|
"Estimating DA3 geometry", 1, 3
|
||||||
|
),
|
||||||
"Estimating DA3 geometry (2/3)",
|
"Estimating DA3 geometry (2/3)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.common_job.frame_progress_message(
|
||||||
|
"Estimating MoGe-2 depth", 0, 1
|
||||||
|
),
|
||||||
|
"Estimating MoGe-2 depth",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.common_job.frame_progress_message(
|
||||||
|
"Estimating DA3 depth", 0, 1
|
||||||
|
),
|
||||||
|
"Estimating DA3 depth",
|
||||||
|
)
|
||||||
|
|
||||||
def test_surface_has_one_model_independent_builder(self):
|
def test_surface_has_one_model_independent_builder(self):
|
||||||
self.assertTrue(callable(self.surface.build_surface))
|
self.assertTrue(callable(self.surface.build_surface))
|
||||||
self.assertTrue(callable(self.surface_outputs.write_surface_archive))
|
self.assertTrue(callable(self.surface_outputs.write_surface_archive))
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ sys.path.insert(
|
|||||||
|
|
||||||
from snapform.blender.depth_plane import (
|
from snapform.blender.depth_plane import (
|
||||||
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME,
|
DEPTH_PLANE_MATERIAL_NODE_GROUP_NAME,
|
||||||
MATERIAL_COLOR_SPACE,
|
material_color_space_name,
|
||||||
SHADELESS_NODE_GROUP_NAME,
|
SHADELESS_NODE_GROUP_NAME,
|
||||||
SURFACE_MATERIAL_NODE_GROUP_NAME,
|
SURFACE_MATERIAL_NODE_GROUP_NAME,
|
||||||
add_depth_plane_processing,
|
add_depth_plane_processing,
|
||||||
@@ -273,7 +273,9 @@ def main():
|
|||||||
"Shadow or Reflection?": "MAXIMUM",
|
"Shadow or Reflection?": "MAXIMUM",
|
||||||
"Shadow, Reflect or Refract?": "MAXIMUM",
|
"Shadow, Reflect or Refract?": "MAXIMUM",
|
||||||
}
|
}
|
||||||
assert foreground.colorspace_settings.name == MATERIAL_COLOR_SPACE
|
assert foreground.colorspace_settings.name == material_color_space_name(
|
||||||
|
bpy.context.scene
|
||||||
|
)
|
||||||
surface_material = create_material("Surface", foreground, 1.0)
|
surface_material = create_material("Surface", foreground, 1.0)
|
||||||
surface_group = bpy.data.node_groups[SURFACE_MATERIAL_NODE_GROUP_NAME]
|
surface_group = bpy.data.node_groups[SURFACE_MATERIAL_NODE_GROUP_NAME]
|
||||||
surface_group_nodes = [
|
surface_group_nodes = [
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ sys.path.insert(
|
|||||||
)
|
)
|
||||||
|
|
||||||
from snapform.blender.depth_plane import MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME
|
from snapform.blender.depth_plane import MOGE_SURFACE_MATERIAL_NODE_GROUP_NAME
|
||||||
|
from snapform.blender.depth_plane import material_color_space_name
|
||||||
from snapform.blender.surface import (
|
from snapform.blender.surface import (
|
||||||
MOGE_NODE_GROUP_NAME,
|
MOGE_NODE_GROUP_NAME,
|
||||||
build_image_mesh,
|
build_image_mesh,
|
||||||
@@ -198,7 +199,9 @@ def main():
|
|||||||
if node.bl_idname == "ShaderNodeTexImage" and node.image == foreground
|
if node.bl_idname == "ShaderNodeTexImage" and node.image == foreground
|
||||||
)
|
)
|
||||||
assert color_texture.image.name == "MoGe Surface Test.color"
|
assert color_texture.image.name == "MoGe Surface Test.color"
|
||||||
assert color_texture.image.colorspace_settings.name == "AgX Base sRGB"
|
assert color_texture.image.colorspace_settings.name == material_color_space_name(
|
||||||
|
bpy.context.scene
|
||||||
|
)
|
||||||
normal_texture = next(
|
normal_texture = next(
|
||||||
node for node in surface.data.materials[0].node_tree.nodes
|
node for node in surface.data.materials[0].node_tree.nodes
|
||||||
if node.bl_idname == "ShaderNodeTexImage"
|
if node.bl_idname == "ShaderNodeTexImage"
|
||||||
|
|||||||
Reference in New Issue
Block a user