- Implement JobClient for handling HTTP requests to the JobServer. - Create ModalJobOperatorBase for managing Blender job lifecycle. - Introduce JobContext and JobServer for job management and state handling. - Add ServiceConnection and ServiceController for managing service processes. - Implement job submission, cancellation, and resource management endpoints in the server. - Ensure proper error handling and job state updates throughout the service.
16 KiB
Surface
Image Empty 转透视网格
- 在 3D View 选中仅包含一帧的 Image Empty,右键打开
SnapForm子菜单并选择Convert to Surface。该入口独立于Convert to Depth Plane,当前不处理视频或序列帧。 模型列表包含三个 MoGe-2 Normal 模型和两个 DA3 模型,默认使用 MoGe-2 ViT-S Normal。DA3 的 Resolution Scale 与 Depth Plane 使用相同设计;MoGe-2 改为显示共享的 Resolution Level。 - Service 请求 DA3 的
depth、conf与intrinsics。仅在开启Remove Background时运行 BEN2 取得 前景 Alpha,并在输出前删除包含背景顶点的三角面;关闭时不加载 BEN2。Depth 不做 smoothing,Service 反投影深度网格,建立源三角面,计算 confidence percentile 与 3×3 相对深度不连续度,并写入 基于原图像素坐标的源UVMap。这些步骤只准备修改器的输入网格,不执行 Relax、拓扑修复、平滑、 小岛清理或最终 UV 重投影。运行时显示的六个Preparing surface data步骤均指这段 Service 端源数据准备。 - Blender 把勾选
Usage > Modifier的O DA3 Surface节点组直接作为 Geometry Nodes 修改器 添加到 Surface,不另设修改器名称。节点组内没有子节点组,依次完成:- 修改器顶层依次显示
Resolution、Relax Iterations、Smooth;默认展开的CullingPanel 依次包含Depth Continuity与Confidence。前者直接读取面域 3×3 相对深度差,并使用指数映射: 默认50%对应官方rtol=0.04,数值越大,内部阈值越小,剔除的断层面越多; - 每轮 Remesh Iteration 依次执行 Connected Merge、循环拓扑修复与一次 Smooth,使新拓扑在进入下一轮 Merge 前先稳定;拓扑修复删除非流形面、单面拓扑和小面积 mesh islands;
- 默认
Resolution为原始有效三角边长中位数乘以sqrt(10) * 2。sqrt(10)来自原先 90% 面数缩减 的二维边距估计,再乘以 2 为迭代合并留出更平滑、稳定的默认尺度;节点内部 Connected Merge 的 Distance 为Resolution * 0.5; - Smooth 采用
O Smooth的边界检测和模糊法线权重,固定开放边界与薄面,只保留一个Smooth程度系数;节点组不再额外执行 Flip Faces; - 从修改前的原始表面以 nearest-surface 重新采样
UVMap,最终明确写为 Corner 域2D Vector。 MoGe-2 使用独立的O MoGe2 Surface。Service 由内置 NumPy 网格构建函数 生成有限深度范围内的紧凑三角网格,并保留模型 mask、逐点 depth 及相邻点相对深度差。Geometry Nodes 按官方mask & ~depth_map_edge(depth, rtol=0.04)的含义,先由Mask删除 mask 外的面,再由Depth Continuity按同一份 3×3 邻域相对深度差删除断层面。DA3 与 MoGe-2 使用相同的属性、 名称、指数滑块和 FACE 删除逻辑,不再额外删除孤立点。之后才执行拓扑修复、Relax、孤岛清理与 UV 重投影。 节点组不提供 Confidence 或 Remesh 开关;Remesh 仍是节点处理流程的一部分。
- 修改器顶层依次显示
- 节点组随插件放在
O_SnapForm.blend。Blender 4.5 首次 Append 后复用同一 datablock; Blender 5.0 以 linked ID 载入后调用 Pack,后续 Surface 继续复用。插件不会为每个 Surface 新建节点组, 也不会在添加后重命名 Geometry Nodes 修改器。节点组只启用Usage > Modifier,不 Mark as Asset; 修改器关闭show_group_selector。Fix Topology位于默认折叠的OptionsPanel。 节点图按 Culling、Remesh、Topology Fix、Island Cleanup 与 UV 分区摆放;各区就近使用独立的 Group Input,并隐藏没有连线的接口,避免跨越整个节点图的输入连线。O DA3 Surface与O MoGe2 Surface的Resolution默认值均为0.1,所有输入 socket 的 Structure Type 均固定为Single。 - Service 准备过程固定显示 6 个编号阶段:验证 DA3 数据、反投影、准备 DA3 与可选 BEN2 数据、构建原始
源三角面、源 UV/attributes、写出网格。状态栏显示
Preparing surface data n/6: ...。 - Service 在 job 根目录写出
mesh.npz与texture.png。NPZ 保存 camera-spacevertices、faces、uv、intrinsics、image_size与有效深度中位数reference_depth。Blender 读取这些 数据后直接创建 Surface 与原图材质,不需要 Open3D 或 OpenCV 进入 Blender Python 环境。 DA3 Surface 只保留 Geometry Nodes 后处理实际使用的 attributes:点域o_depth、面域o_face_confidence与面域o_depth_discontinuity。Remove Background 已在 Service 建面前直接删除背景三角面,不写入任何 background attribute。MoGe-2 的导入网格保留点域o_depth, 以及面域o_mask、o_depth_discontinuity,不伪造 confidence、stretch 或 background 属性, 也不写入 custom normal。完整分辨率的normal_map保存在 NPZ 中并转换为材质贴图。 所有 MoGe-2 Image datablock 都以源图片名而非对象名为基名:depth、normal 与 mask 分别使用图片名.depth、图片名.normal、图片名.mask,不附加.moge2。 - 网格以
reference_depth截面匹配 Image Empty 的显示矩形:该截面的投影四角与 Empty 的可见 边界重合,主点同时匹配 Empty Offset。网格对象直接沿用源 Empty 的matrix_world,因此位置、旋转、 缩放及父级关系产生的世界变换保持一致。更近的顶点沿 Empty 局部+Z方向凸出,三角面正面朝向 观察相机,启用 Backface Culling 时可从正面看到。 - 转换对话框不再提供后处理参数,只保留模型与 Resolution Scale 设置、
Create Matching Camera、Remove Background、Unshaded,以及分割线下方的Keep Original。背景移除默认关闭;开启后 Service 使用 BEN2,并在写出 Surface 前直接删除背景三角面,不再由 Geometry Nodes 的 Background 开关处理。 该预删除同时适用于 DA3 与 MoGe-2;因此 MoGe-2 转换后即使关闭Mask,BEN2 已删除的背景也不会恢复。Unshaded使用与 Depth Plane 相同的 Emission/Transparent 材质路径;普通材质的 Bump Scale 按abs(object.scale.z) × camera scale_z设置,并在材质组内乘以固定基准0.001, 与透视表面实际深度尺度一致;它允许负数以反转凹凸方向。 相机默认关闭;开启后会在 Empty 局部+Z侧创建一台朝向局部-Z的透视相机,并成为生成 Surface 的子级,使二者移动时保持投影关系。关闭Keep Original时,网格成功创建后删除源 Empty。 表面、材质与相机会归入同一个Convert to SurfaceUndo 步骤。
Process Res 的设计依据 DA3 官方实现:API 与 CLI 默认使用 process_res=504 和
process_res_method="upper_bound_resize";输入预处理的 PATCH_SIZE=14,会把宽高分别调整到最接近的
14 倍数。DA3 论文以 504×504 为 base resolution,公开的训练尺寸长边为 504、672、756 或
896。官方 CLI 虽给出 1024 示例,但没有声明更高分辨率会提升质量,也没有规定 1000 为硬上限。
因此转换入口使用相对原图的 Resolution Scale,同时通过上下限避免 Process Res 过低或过高,并把训练范围
之外的值明确视为实验设置。参考
DA3 API、
输入预处理实现
与技术报告。
Debug 的 Textured Surface GLB 会把 depth、confidence 与 confidence percentile 写入自定义顶点
attributes。其他 DA3 Debug 三维输出会同时写出未压缩的 prediction.attributes.npz,保存完整
depth、confidence、intrinsics、extrinsics 与 confidence percentile。当前 trimesh 的 glTF 点图元
导出器只写 Position/Color,无法可靠把任意 attribute 嵌入点云 GLB,因此点云先以 companion NPZ
保留完整结构;后续若改为 SnapForm 自有 glTF writer,可再把这些字段直接嵌入点图元。
所有任务使用统一的状态栏进度显示。变化中的阶段说明显示在右侧的进度条内部,取消按钮 位于进度条右侧。状态字段支持阶段、当前操作与预计剩余秒数,界面统一组合为紧凑文案并保证 进度单调递增。任务进程与 Service 请求在后台启动,按钮点击后立即进入进度状态。Image Depth 的阶段包括 Environment 启动、输入准备、模型加载、深度估算、缓存 写出和面片构建。进度更新会强制刷新状态栏与窗口,无需移动鼠标即可实时显示。安装中断后残留的 半成品 Environment 会在下次安装前自动清理,不会因为中断而无法再次安装。
导入过程精确保留 NPZ 内深度值之间的差值。例如深度 2.0 和 5.5 在
Displacement Strength = -1 时,沿局部 Z 轴相差 3.5 Blender Unit。它不会把数值压缩到
0..1,也不再提供视觉归一化模式。
面片尺寸以有效深度的中位数 Zref 作为参考截面。先用 mini_npz 中的相机内参计算原始
投影面积,再恢复源图纵横比:
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,便于检查实际采用的
参考深度和尺寸。
材质始终导入深度 Image Texture,并连接到 Displacement 节点和 Material Output;材质 Surface
Displacement 设置为 Displacement Only,DA3 与 Original 都使用 Midlevel = 0、Scale = 1。MoGe-2 的位移只在专用 Geometry Nodes 中处理。
o_material_displacement 为 False 时,材质把 Height 乘以零,置换只在几何节点中生效;为 True 时,
几何节点关闭结构置换,材质 Height 恢复。色彩贴图的 Alpha 二值结果同时用于选择原始深度或有符号的
Background Depth,使两种置换路径保持一致;未移除背景的普通不透明图片因此全部视为前景。
视频与序列帧的颜色、深度与前景 Image Texture
通过 image_user 的 Frames / Offset / Start 把第 i 帧映射到场景 Start+i 帧,并启用 Auto Refresh。
颜色贴图到 Principled BSDF 之间使用独立的 Shader Node Group。DA3 Surface 使用 O DA3 Surface Layer,
MoGe-2 Surface 使用独立的 O MoGe2 Surface Layer;它额外接收模型输出的 Normal 贴图,使用
Normal Map 节点按 Object Space 解码后连接到 Bump 的 Normal 输入,使像素级模型法线与原图
Bump 合并,不依赖重拓扑后的顶点密度;Normal Strength 默认值为 1.0,只调节模型 normal map,
原图 Bump 仍由独立的 Bump Scale 控制;
Depth Plane 使用 O DA3 Depth Plane Layer 材质组,并把背景剔除、背景深度与材质置换 Attribute
读取全部收进组内。两者都接收 Color、Alpha 与 Bump Scale,只有 O DA3 Depth Plane Layer
额外接收 Float 类型的 Depth。
O DA3 Surface Layer 只输出 Color、Roughness、Alpha 与 Normal,以原图 Color 保留 Bump 效果,
不创建 Displacement 节点或输出;O DA3 Depth Plane Layer 另外输出 Displacement。外层材质只保留贴图、
材质组、Principled BSDF 与 Material Output,方便继续调整。
Depth Plane 的 Bump Height 直接使用材质组的 Color 输入,只受独立的 Bump Scale 影响,不使用深度贴图,
也不经过 Displacement 模式开关、Depth Scale 或 Background Depth;材质 Displacement 仍使用这些参数处理后的深度。
无论选择哪种置换方式,原始彩色贴图都会连接
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。开启 Remove Background 时色彩贴图使用 BEN2 输出;关闭时使用原图。
这里的“原始距离”是 DA3 模型输出的尺度。普通 Small、Base、Large 和 Giant 模型的深度不应
被解释为现实世界的米;需要现实尺度时应选择带 metric depth 的
DA3 Nested Giant Large 1.1。此外,当前结果仍是固定 XY 平面上的深度位移面,不是使用
相机内参逐像素反投影得到的完整 camera-space 网格;中位深度截面为 Geometry Nodes
深度面片提供正确的整体高宽尺度。
生成完成后会直接创建面片,不提供缓存重建入口或单独的导入参数。重新选择
Convert to Depth Plane 会按当前 Depth from 再次转换。
面片、深度 Image、前景 Image、Material 与 Geometry Nodes 修改器会在同一个 Blender Undo 步骤中创建;转换完成后执行
Ctrl+Z 会完整撤销本次转换,不会越过转换去回退更早的 Image Empty 操作。
Image Empty 使用 Packed Image 时,SnapForm 会通过 Blender 写出临时 PNG 交给 DA3,
不会 Unpack 或修改源 Image;任务结束后自动删除临时文件。普通图片仍直接读取源文件。DA3 的原始
results.npz 保留在对应 job 文件夹根下,生成的浮点深度 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 等编号。
视频输入时 Blender 无法整体 Pack 序列帧,因此 .depth 与颜色序列改为引用 job 目录根下的
depth.0001.exr、frame.0001.jpg(.frame)或 foreground.0001.png,并在每帧保留原始
浮点深度;保存 .blend 后再次打开仍需对应 job 目录存在。
选择 PLY 后,模型菜单只会提供 DA3 Giant 1.1 和
DA3 Nested Giant Large 1.1。Small 和 Base 没有 Gaussian head,无法由
DA3 生成 gs_ply。
每个 Service Job 被接受时,BlendJob 在 ~/.snapform/jobs/ 下创建一个独立文件夹。文件夹名是 32 位 opaque Job ID,不包含任务类型或对象名称。执行逻辑从 JobContext.directory 获取路径,输出产物直接写入该目录。
Blender 为 Packed Image 或序列帧生成的输入副本先保存在系统临时目录,Service 读取完成后由 Blender 清理,不归档到 Job 目录。普通磁盘文件和视频仍直接读取原文件。Service Job 日志统一写入 service.log,进度只保存在 Service 内存中。Environment Bootstrap 使用 Storage Root 下的 environment.log 与 environment-status.json,不创建 Job 目录。
与几何输出相关的 Job Type 为 generate-da3-depth、generate-moge2-depth、generate-da3-surface、generate-moge2-surface、debug-da3 与 debug-moge2。每种 Job 只接收对应模型与输出所需的参数。
DA3 ONNX exporter 产生的 depth_vis/、exports/mini_npz/ 会在完成后自动平铺,
不会保留只含一层产物的子目录:
<opaque-job-id>/
├── scene.glb、*.ply、*.jpg 或 results.npz
└── 其它对应 Debug 格式的产物
<opaque-job-id>/
├── foreground.png 单图结果
├── foreground.0001.png… 视频/序列帧结果
├── frame.0001.jpg… 视频/序列帧的源帧副本
└── depth.0001.exr… AI 深度序列帧
建议从 DA3 Base 开始。它使用 Apache 2.0 模型权重。