# Cutout Tool Cutout 从图片上的一个 Selection 开始,可按需生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。 ## 执行过程 ```mermaid flowchart TD Select["圈定 SelectionPath"] Menu["打开 Shape Pie Menu"] Shape["选择 Shape"] Raster["SelectionPath → 几何 SelectionMask"] Route{"需要后台生成数据?"} Local["读取局部 RGBA
源 Alpha × Selection 值"] Bounds["写出几何 bounds 矩形 RGBA"] Job["生成 Depth / Normal 产物"] Encoded["加载并 Pack 输入 RGBA"] Ready["贴图 Alpha × Selection Mask"] Build["构建 BaseShape"] Create["创建 Blender 对象"] Select --> Menu --> Shape --> Raster --> Route Route -- "否" --> Local --> Ready Route -- "是" --> Bounds --> Job --> Encoded --> Ready Ready --> Build --> Create ``` Surface 与 Balloon 在本地组合源 Alpha 与几何 Selection 后构建 `BaseShape`;Depth Shape 和 Normal Map 先通过同一个矩形图片协议生成所需产物。 ## 节点说明 ### 圈定 Selection,选择 Shape Cutout 工具由 `__init__.py` 注册,交互从 `main.py` 中的 `SelectCutoutSelection` 开始。它接收 Lasso 或双击整图手势,只构建内存中的 `SelectionPath`,然后调用 `interaction.py` 打开 Shape 饼菜单。此时不读取源图像素、不栅格化 Mask,也不扫描可见 Alpha。 对象命中由 Blender 原生点选决定。普通点击另一对象时只切换 active;空白单击保留当前 active Image Empty。Lasso 可从透明区域或 Viewport 空白开始拖动,双击也可从空命中位置打开整图 Shape 菜单。起始点不判断图片范围或 Alpha,完整 Selection 的几何与内容有效性在后续栅格化和 Shape 构建阶段判断。 `interaction.py` 读取工具选项,并在构建菜单按钮时把只含 Points 的完整 `SelectionPath` 编码一次,同时把当前 **Inward Axis** 固定到各 `CutoutSelectionToShape`。这次编码只用于跨 Blender Operator 传值,不属于 Selection Mask 构建。**Mesh Detail** 的 Low、Medium、High、Ultra 分别携带 32、16、8、4 px 的源图采样间距;档位越高,采样越密。菜单中的 Surface、Balloon、Depth Balloon 和 Depth Surface,以及它们对应的节点分支索引,统一来自 `shape.py`。 ### 栅格化 Selection 用户选定 Shape 后,`main.py` 中的 `CutoutSelectionToShape` 接过主线。它从自身 Property 恢复 `SelectionPath`,再使用 `common/selection.py` 以 non-zero winding 在 Path Bounds 与 2 px 抗锯齿边缘内栅格化为一次纯几何 `SelectionMask`。`values` 保存局部几何权重,`bounds` 保存它在源图中的位置;栅格阶段不读取、接收或合并源 Alpha。 实际 Cutout 业务随后读取一次源 RGBA。无需 AI 时,它在几何 bounds 内显式计算 `source_alpha × selection_values`,由该内容 Mask 判断空内容并约束轮廓;原始 `SelectionMask` 不被修改。此时按 `Selection bounds 长边 ÷ Mesh Detail 像素间距` 计算实际采样长边;只有它超过 **Maximum Cutout Mesh Resolution** 时才等比增大有效间距。bounds 的原始像素长边本身不会触发降密。 ### 需要后台生成数据? 选择 Depth Balloon、Depth Surface 或开启 **Normal Map** 时需要提交 Job。 Surface 和 Balloon 的本地路径直接使用本地内容 Mask。 ### 生成 Depth / Normal 产物 `main.py` 先通过 `common/image.py` 把 Selection 几何 bounds 内的原始 RGBA 写成矩形输入。该文件保留源 Alpha,不预乘 Lasso 值;文件、Packed 或内存图片都使用同一个入口。随后把请求提交给 `server/jobs/cutout.py`,生成本次 Shape 需要的产物: - 需要 Vector Depth 时写出相机空间 XYZ 组成的 `vector-depth.exr`,需要 Z Depth 时写出 `z-depth.exr`,两者都配套 `depth.json`; - **Normal Map** 根据既定空间写出 `tangent-normal.png` 或 `object-normal.png`。 Depth 与 Normal Map 共享同一个 MoGe-2 `GeometryFrame`。模型输入先按 Alpha 合成到黑底;Vector/Z Depth 的 RGB 保留完整连续 Field,Alpha 保存模型 Validity。Depth Surface 的 Normal Map 编码为 Cutout 原生 XY / Z 对象空间,正面中性法线为局部 `+Z`;其余 Shape 使用 Tangent Space。`main.normal_mode_for_shape()` 根据 Shape 确定 Normal Space。 **Maximum AI Input Size** 约束 MoGe-2 的分析图长边;网格密度使用 **Maximum Cutout Mesh Resolution**,最终颜色图使用 Selection bounds 分辨率。 Color 使用 Selection bounds 内的源 RGBA;Normal 的可见区域、MoGe-2 黑底输入和 Depth Metadata 的参考区域使用源 Alpha。Depth EXR 的 RGB 保存连续 Field,Alpha 保存模型 Validity。Selection Mask 只在 Blender 构建网格时与贴图 Alpha 相乘。 ### 构建 BaseShape Job 完成后,`main.py` 在 Blender 主线程加载并 Pack 可选的 Depth 与 Normal 图片,并复用提交前的矩形 RGBA 输入作为颜色图。它从同一 Image 读取 Alpha,与已栅格化的 Selection Mask 相乘后构建网格内容 Mask;颜色及 AI 贴图保持原 Alpha。`main.create_cutout_shape()` 调用 `geometry.py` 中的 `build_base_shape()`: 1. 从内容 Alpha 值提取外轮廓和孔洞; 2. 按 Mesh Detail 对应的采样间距生成轮廓与内部采样点; 3. 进行约束三角化; 4. 计算四种 Shape 共用的 `o_balloon`; 5. 汇总顶点、三角面、UV 和 Balloon 权重,得到内存中的 `BaseShape`。 `geometry.py` 还提供 Depth 标定、采样和网格尺度计算,供后面的对象创建阶段使用。 ### 创建 Blender 对象 `main.create_cutout_shape()` 把 `BaseShape`、颜色 Image 和可选产物交给 `object.py` 的 `create_shape_object()`。这里创建 Material、Mesh Object,并挂上统一的 `O Image Cutout` Modifier。材质通过 `common/material.py` 创建,Cutout 使用 `1.2` 的 IOR,并由 `O Image Layer` Color 直接驱动 Principled BSDF 的 IOR Level。本地 Cutout 创建一次几何 bounds 颜色图;AI Cutout 直接复用已编码并 Pack 的矩形输入。 | Shape | Modifier 如何形成结果 | 必需后台产物 | | --- | --- | --- | | Surface | 保持基础平面轮廓,可增加真实厚度 | 无 | | Balloon | 使用 `o_balloon` 从边缘向内部鼓起 | 无 | | Depth Balloon | 在 Balloon 上叠加相对基准面的深度 | `z-depth.exr`、`depth.json` | | Depth Surface | 使用相机点 XYZ 重建透视表面 | `vector-depth.exr`、`depth.json` | Depth Shape 的 Reference Depth,以及 Depth Balloon 的 Direction,都由 `object.py` 调用 `geometry.py` 的标定函数,根据最终网格内容 Mask 计算。透明像素达到 `5%` 时使用有效内容区域自适应标定,否则使用稳定的固定基准。 Normal Map 关闭时材质使用几何法线;开启时,`object.py` 把生成的图片和已经确定的 Normal Space 交给材质节点。最后 `main.py` 提交 Undo,整条 Cutout 流程结束。 ### Inward Axis ```mermaid flowchart TD Base["BaseShape 与四种 Shape
原生 XY / Z"] --> Switch{"Inward Axis"} Switch -- "-Z(默认)" --> Native["节点几何直接输出
对象沿用源 Empty local transform"] Switch -- "+X" --> Orient["O Image Cutout 执行 Z → X
对象矩阵执行 X → Z"] Native --> World["相同世界空间结果"] Orient --> World Normal["Depth Surface
Object Space Normal"] --> Mode{"Inward Axis"} Mode -- "-Z" --> Keep["保持原生 Z 轴通道"] Mode -- "+X" --> Swizzle["按 Z → X 转换一次"] ``` BaseShape 与 Shape 子节点始终在局部 XY 平面建模,并沿 `-Z` 向图片内部生成厚度和深度。统一的 `O Image Cutout` Modifier 只在选择 `+X` 时转换最终几何;`object.py` 同步使用逆矩阵,因此默认 `-Z` 与源 Image Empty 的 local transform 一致,`+X` 模式仍保持相同世界空间形状。Depth Surface 的 Object Space Normal 默认直接使用原生 Z 轴 Artifact,仅在 `+X` 模式转换通道;Tangent Space Normal 不变。 Balloon 与 Depth Balloon 的材质会创建 Geometry Attribute 节点,把 `o_balloon` 的 Float 输出直接连接到 `O Image Layer` 已有的 **Normal Scale**;Surface 与 Depth Surface 不建立这条连接。