feat(selection): separate selection geometry from image content processing

- Introduced a new design for selection rasterization that decouples the selection geometry from the image content, ensuring that the SelectionMask is independent of the source image's alpha and color.
- Updated the `rasterize_selection_path` function to only accept selection paths and image dimensions, removing parameters related to source alpha and alpha thresholds.
- Implemented a new approach for handling selection paths using non-zero winding fill, ensuring overlapping areas are preserved.
- Enhanced performance by utilizing a scanline algorithm for rasterization, significantly reducing processing time for high-resolution images.
- Updated internal documentation and added comprehensive tests to validate the new behavior and performance improvements.
- Ensured backward compatibility by maintaining existing interfaces where necessary while introducing breaking changes in the selection processing logic.
This commit is contained in:
2026-09-01 22:36:20 +08:00
parent 2ff7b04f56
commit 7f6e47432e
54 changed files with 2831 additions and 634 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-01

View File

@@ -0,0 +1,136 @@
## Context
Convert to Plane 当前使用 Image Empty 的局部显示边界直接创建 Mesh,再复制源对象矩阵。非居中的 `empty_image_offset` 因此被写入顶点坐标,结果外观位置正确,但对象原点不在面中心。
`O Image Depth Plane` 当前先让 `O Mesh Plane` 生成零厚度网格,再进行一次挤出并移动前侧。该拓扑没有沿厚度方向的中间环;运行时又把默认 `Subdivide` 写为 `0`,深度图通常没有足够顶点形成地形。它还使用与 Cutout 不一致的 `Depth Amount`、`Reference Depth` 和重复的 `Material` 输入。
对象创建代码已经把图像材质追加到 Mesh 第 0 个材质槽。`O Image Cutout` 直接依赖该槽,而 `O Mesh Plane` 额外通过 `Material` socket 和 Set Material 节点重复同一职责。
Depth Plane Job 与 Cutout Job 已共同调用 `generate_moge2_artifacts()`,但 Depth Plane 当前只请求 Z Depth。共享生成层能够从同一个 MoGe-2 `GeometryFrame` 同时写出 Z Depth 与 Tangent Normal,因此不需要第二次模型推理。
## Goals / Non-Goals
**Goals:**
- 让普通 Plane 和 Depth Plane 以面中心为对象原点,并严格保留原显示矩形的世界空间位置。
- 把 Depth Plane 建模为一面置换、背面固定、侧壁分层渐变的地形体块。
- 统一 Depth Plane 与 Cutout 的 `Depth Scale`、`Base Plane Depth` 术语。
- 让 Mesh 材质槽承担材质归属,并让纯生成的 Plane 几何保留输入 Mesh 的材质集合。
- 让 Depth Plane 的几何置换与材质法线共享一次 MoGe-2 预测。
- 用节点接口、求值几何和世界空间位置测试固定行为。
**Non-Goals:**
- 不修改 MoGe-2 推理、Z Depth EXR、Validity Alpha 或 `depth.json` 协议。
- 不按每张深度图归一化位移,也不让 `Thickness` 限制正向地形高度。
- 不增加侧壁环数量、插值曲线或位移方向等新控制项。
- 不修改 Cutout 的几何或材质行为。
- 不合并 Cutout 与 Depth Plane 的顶层 Job 编排。
## Decisions
### 1. 通过局部重心与矩阵补偿移动对象原点
设 Image Empty 局部显示边界中心为 `C`、原矩阵为 `M`。新 Mesh 使用 `P - C` 作为顶点坐标,新对象矩阵使用 `M × Translation(C)`,因此:
```text
(M × Translation(C)) × (P - C) = M × P
```
普通 Plane 和 Depth Plane 共用同一套居中网格与放置计算,UV 仍覆盖 `0–1`。该方法不调用依赖当前选择和 Context 的 Origin Operator,也能正确保留旋转、非均匀缩放和 Image Empty 偏移。
不选择先创建对象再调用 `origin_set`,因为该 Operator 依赖 Blender Context,并会把稳定的坐标变换变成隐式场景操作。
### 2. 纯生成几何显式保留 Mesh 材质集合
从 `O Mesh Plane` 删除 `Material` interface socket;`O Image Depth Plane` 同步删除对应 socket 与转发。运行时继续在添加 Modifier 前执行 `mesh.materials.append(material)`。
Mesh Grid 和 Mesh Cube 是完全生成的几何,仅设置材质索引 `0` 不会自动保留输入 Mesh 的 Material Set。因此 `O Mesh Plane` 先在输入几何上捕获恒真匿名属性,再把输入几何与生成几何临时 Join,统一设为材质索引 `0`,最后按捕获属性删除全部输入点。输出只剩生成几何,但仍携带输入 Mesh 的材质集合。验证脚本将求值普通 Plane、厚度体块和 Depth Plane,检查求值 Mesh 保留第 `0` 个材质槽且所有面使用索引 `0`。
不保留隐藏 `Material` socket,因为它仍会形成第二条材质来源,并允许 Modifier 输入与对象材质槽漂移。
### 3. Depth Plane 直接复用 O Mesh Plane 的厚度拓扑
`O Image Depth Plane` 将输入 Geometry、`Subdivide` 和 `Thickness` 交给 `O Mesh Plane`。`O Mesh Plane` 的 Cube 路径已经按表面网格步长计算厚度方向分段;该计算增加最少侧壁分段,保证固定底面与可变面之间存在中间环,但不增加公开控制项。
`O Mesh Plane` 的体块以原平面为中心,Depth Plane 在内部沿局部 Z 平移 `Thickness / 2`,得到:
```text
固定底面 z = 0
未置换可变面 z = Thickness
```
`Thickness = 0` 时保留单张置换表面,不生成重叠的背面或侧壁。
不继续使用单次 Extrude,因为它只生成跨越完整厚度的侧面,无法表达逐环渐变;也不复制一套 Grid/Cube 构建逻辑,避免与 `O Mesh Plane` 的纵横比分段和 UV 规则漂移。
### 4. 使用同一个标量场驱动正面和侧壁环
每个 XY 位置从 `z-depth.exr` 采样 Z Depth。隐藏的 `Uniform Scale` 把相机深度转换为对象局部距离,完整正面位移为:
```text
depth = sampled_z × Uniform Scale
full_offset = (Base Plane Depth - depth) × Depth Scale
```
这使较近深度向局部正 Z 突出,较远深度向固定底面内缩,等于 `Base Plane Depth` 时位移为零。连续 Depth RGB 在 Validity 边界仍被采样,不用 Alpha 删除点,与当前 Artifact 协议保持一致。
体块顶点以未置换位置计算层权重:
```text
layer_weight = clamp(z / Thickness, 0, 1)
final_z = z + safe_full_offset × layer_weight
```
固定底面权重为 `0`,可变面为 `1`,中间环线性递增。线性插值比 Smooth Step 更直接地表达各层置换比例,也让截面顺序容易验证。
### 5. 负向位移限幅,正向位移保持完整
`safe_full_offset` 的下限设为略大于 `-Thickness`。因为所有层使用同一限幅结果乘以单调层权重,侧壁环不会反序,可变面也不会与固定底面重合。
正向位移不钳制。`Thickness` 表示固定底面到未置换可变面的基础厚度;固定底面始终停在原图片平面,`Depth Scale = 0` 时实际厚度恰好等于 `Thickness`,启用正向置换后最终包围盒可以更高。
不把位移归一化到固定包围盒,因为这会让相同 `Depth Scale` 在不同图片上产生不同物理尺度,并破坏固定底面、基础厚度和自由 Depth Scale 的独立语义。
### 6. 一次性替换节点接口与默认值
`Depth Amount` 直接改为 `Depth Scale`,`Reference Depth` 直接改为 `Base Plane Depth`,不添加旧名称转发。创建 Depth Plane 时继续从 Metadata 初始化基础深度与 Uniform Scale,并将 Depth Plane 的 `Subdivide` 默认值设为 `6`;普通 Plane 的默认细分保持 `0`。
节点资产、Modifier 输入设置、验证、测试和文档在同一次变更中更新并重建,不保留旧 `.blend` 接口。
### 7. 一次预测同时生成几何深度与材质法线
Depth Plane 顶层 Job 继续负责完整图片的颜色复制和结果协议,并调用共享的 `generate_moge2_artifacts()`,同时传入:
```text
depth_mode = Z
normal_mode = TANGENT
```
共享生成层只执行一次 MoGe-2 inference,并从同一个 `GeometryFrame` 写出 `z-depth.exr`、`depth.json` 和 `tangent-normal.png`。Cutout 顶层 Job 仍保留 Selection 裁切、可选 BEN2 Refine 和自身结果编排;两条工作流只共享与图片选择无关的 MoGe-2 Artifact 生成层,不把 Cutout 分支引入 Depth Plane。
Blender 响应阶段使用现有 Normal 图片加载入口把 `tangent-normal.png` 设为 Non-Color 并 Pack,再以 `normal_space="TANGENT"` 和 `ior=1.2` 传给 `create_image_material()`,使 Depth Plane 与 Cutout 使用相同的 Principled IOR。Normal Map 只参与材质着色,`z-depth.exr` 继续单独驱动 Geometry Nodes 置换;任一步失败时清理尚未被数据块使用的 Depth 与 Normal 图片。
## Risks / Trade-offs
- [高默认细分与厚度环会增加求值几何量] → 继续使用 `O Mesh Plane` 的纵横比自适应分段,并让用户通过现有 `Subdivide` 降低精度。
- [极端负向位移会形成非常薄的楔形侧壁] → 在固定底面前保留小间隙,并用求值测试检查层顺序和非穿透。
- [删除 Material socket 后纯生成几何会丢失 Material Set] → 临时 Join 带匿名标记的输入几何,设置索引后删除输入点,并对普通 Plane、厚度体块、Depth Plane 和剪贴板 Plane 做真实 Blender 求值。
- [Depth 与 Normal 分开生成会重复执行 MoGe-2] → 由共享 Artifact 生成层从同一个 `GeometryFrame` 写出两类文件,并测试 inference 只调用一次。
- [Tangent Normal 可能未进入最终材质或使用错误色彩空间] → 检查图片为 Non-Color、已 Pack,材质 Normal Texture 使用 Tangent Space 且连接到现有 `O Image Layer` 输入。
- [移动原点可能影响依赖旧局部坐标的外部脚本] → 这是预期的对象语义修正;世界空间几何保持不变,不提供兼容路径。
- [节点资产文档与当前源码已有少量名称漂移] → 在本次变更中按最终接口同步更正对应 Plane 文档,不扩展到无关主题。
## Migration Plan
1. 先修改中心原点的 Mesh 与对象矩阵计算,并固定普通 Plane、Depth Plane 的世界空间回归测试。
2. 删除 Plane 节点组的材质输入和运行时 Modifier 赋值,增加第 0 材质槽的求值验证。
3. 重建 Depth Plane 节点图、接口名称、默认细分和负向限幅测试。
4. 扩展 Depth Plane Job,让一次 MoGe-2 预测同时生成 Z Depth 与 Tangent Normal,并把 Normal 接入材质。
5. 重建并验证 `O_AnyImage.blend`,运行完整测试,再同步 Plane 与节点资产内部文档。
回退必须同时回退节点构建脚本、发布 `.blend`、运行时输入名称和对应测试,避免节点接口与 Python 写入不一致。
## Open Questions
无。

View File

@@ -0,0 +1,33 @@
## Why
Plane 转换目前保留了 Image Empty 的原点而不是生成面的中心,Depth Plane 也只有单次挤出的侧壁,默认细分不足以表达深度地形。节点组还重复承担材质赋值,导致接口和对象数据的职责不一致。
## What Changes
- 普通 Plane 和 Depth Plane 在保持世界空间位置不变的同时,把对象原点统一放到面的中心。
- 将 Depth Plane 重建为单面地形体块:固定底面留在原图片平面,`Thickness` 沿正法线移动置换面,中间侧壁环按厚度位置渐变置换。
- 明确 `Thickness` 为基础厚度:`Depth Scale` 为零时,体块厚度等于 `Thickness`。
- 让 `Base Plane Depth` 决定深度图的零位移位置,并限制负向置换不穿过固定底面。
- **BREAKING**:将 Depth Plane 的 `Depth Amount` 改为 `Depth Scale`,将 `Reference Depth` 改为 `Base Plane Depth`,不保留旧接口。
- **BREAKING**:从 `O Mesh Plane` 和 `O Image Depth Plane` 删除 `Material` socket;`O Mesh Plane` 在内部保留输入 Mesh 的材质集合,生成面统一使用第 `0` 个材质槽。
- Depth Plane 通过一次共享的 MoGe-2 预测同时生成 `z-depth.exr`、`depth.json` 和 `tangent-normal.png`,并把 Tangent Normal 接入图像材质。
- Depth Plane 图像材质与 Cutout 统一使用 Principled IOR `1.2`。
- 将 Depth Plane 的默认 `Subdivide` 设为 `6`,在地形细节和求值几何量之间保持平衡。
## Capabilities
### New Capabilities
- `plane-conversion`: 规定 Plane 转换的中心原点、材质职责,以及 Depth Plane 地形体块的几何和控制语义。
### Modified Capabilities
无。
## Impact
- 影响 Plane 转换与 Depth Plane 结果创建;删除 `Material` socket 同时简化剪贴板 Plane 的 Modifier 输入。
- 扩展 Depth Plane Job 的返回产物与 Blender 端材质创建,但继续复用 Cutout 已使用的通用 MoGe-2 Artifact 生成层。
- 重建 `O Mesh Plane` 与 `O Image Depth Plane` 节点资产、验证脚本和发布 `.blend`。
- 更新节点组接口、运行时 Modifier 输入、Blender 求值测试和内部文档。
- 不改变 `z-depth.exr`、Depth Metadata 或 Cutout Job 协议。

View File

@@ -0,0 +1,109 @@
## ADDED Requirements
### Requirement: Converted planes use a centered origin
系统 SHALL 让 Convert to Plane 和 Convert to Depth Plane 的结果对象以生成矩形面的中心为对象原点,同时保持源 Image Empty 所表示矩形的世界空间位置不变。
#### Scenario: Convert an offset Image Empty
- **WHEN** 用户转换一个 `empty_image_offset` 不以原点为中心的 Image Empty
- **THEN** 结果对象原点位于矩形面的中心
- **AND** 结果面的四个世界空间角点与转换前 Image Empty 的显示范围一致
#### Scenario: Preserve transformed placement
- **WHEN** 源 Image Empty 带有平移、旋转或缩放
- **THEN** 普通 Plane 和 Depth Plane 均通过对象矩阵保留这些世界空间变换
- **AND** 结果 Mesh 顶点以对象原点为中心分布
### Requirement: Plane node groups retain the Mesh material set
`O Mesh Plane` 和 `O Image Depth Plane` SHALL 不公开 `Material` socket。创建对象的代码 SHALL 把图像材质放入 Mesh 的第 `0` 个材质槽;`O Mesh Plane` SHALL 在删除输入几何前把其材质集合传递给纯生成几何,并让生成面使用材质索引 `0`。
#### Scenario: Evaluate a generated Plane
- **WHEN** 带有第 0 个材质槽的对象通过 `O Mesh Plane` 生成平面或体块
- **THEN** 求值后的 Mesh 保留该材质槽
- **AND** 所有面使用材质索引 `0`
- **AND** Modifier 不包含 `Material` 输入
#### Scenario: Evaluate a generated Depth Plane
- **WHEN** 带有第 0 个材质槽的对象通过 `O Image Depth Plane` 生成地形体块
- **THEN** 求值后的 Mesh 保留该材质槽
- **AND** 所有面使用材质索引 `0`
- **AND** `O Image Depth Plane` 不向 `O Mesh Plane` 传递材质
### Requirement: Depth Plane exposes unified depth controls
`O Image Depth Plane` SHALL 使用 `Thickness`、`Depth Scale`、`Base Plane Depth` 和 `Subdivide` 作为用户控制项。`Depth Image` 与 `Uniform Scale` SHALL 继续作为隐藏数据输入。旧的 `Depth Amount`、`Reference Depth` 和 `Material` 输入 SHALL 不再存在。
#### Scenario: Create a Depth Plane modifier
- **WHEN** Convert to Depth Plane 创建 Modifier
- **THEN** `Depth Scale` 默认值为 `1.0`
- **AND** `Base Plane Depth` 取当前 Depth Metadata 的参考深度乘以 `Uniform Scale`
- **AND** `Subdivide` 默认值为 `6`
#### Scenario: Inspect the node group interface
- **WHEN** 加载发布资产中的 `O Image Depth Plane`
- **THEN** 输入接口包含 `Geometry`、`Subdivide`、`Thickness`、`Depth Scale`、`Base Plane Depth`、`Uniform Scale` 和 `Depth Image`
- **AND** 输入接口不包含 `Depth Amount`、`Reference Depth` 或 `Material`
### Requirement: Depth Plane shares one prediction for depth and material normal
系统 SHALL 让 Depth Plane 通过通用 MoGe-2 Artifact 生成层执行一次模型预测,并从同一个 `GeometryFrame` 生成 `z-depth.exr`、`depth.json` 与 `tangent-normal.png`。Blender SHALL 将 Tangent Normal 作为 Non-Color 图片加载、Pack 并接入结果材质;Normal Map SHALL 只影响材质着色,不参与 Geometry Nodes 置换。
#### Scenario: Generate Depth Plane artifacts
- **WHEN** Depth Plane Job 处理一张完整图片
- **THEN** MoGe-2 inference 只执行一次
- **AND** Job 返回 `z_depth`、`depth_metadata` 和 `tangent_normal`
#### Scenario: Create the Depth Plane material
- **WHEN** Blender 接收成功的 Depth Plane Job 结果
- **THEN** `tangent-normal.png` 以 Non-Color Tangent Space Normal 加载并 Pack
- **AND** 结果材质的 Normal 输入使用该图片
- **AND** 结果材质的 Principled IOR 为 `1.2`
- **AND** `z-depth.exr` 继续单独驱动 Depth Plane 几何置换
#### Scenario: Keep workflow-specific job orchestration
- **WHEN** Depth Plane 与 Cutout 请求 MoGe-2 产物
- **THEN** 两者复用同一个通用 Artifact 生成层
- **AND** Depth Plane Job 不承担 Cutout 的 Selection、Refine Selection 或裁切结果协议
### Requirement: Depth Plane is a single-sided terrain block
系统 SHALL 从 `O Mesh Plane` 的有厚度几何生成 Depth Plane。固定底面 SHALL 位于原图片平面,未置换的可变面 SHALL 位于其正法线方向的 `Thickness` 距离处;只有可变面完整应用深度置换,固定底面 SHALL 完全不动,侧壁中间环 SHALL 按其厚度位置线性渐变置换。
#### Scenario: Disable depth displacement
- **WHEN** `Depth Scale` 为 `0`
- **THEN** 固定底面保持在原图片平面
- **AND** 可变面位于正法线方向的 `Thickness` 距离处
#### Scenario: Change depth displacement
- **WHEN** 用户改变 `Depth Scale` 或 `Base Plane Depth`
- **THEN** 可变面按完整位移变化
- **AND** 固定底面的所有顶点位置保持不变
#### Scenario: Interpolate side-wall rings
- **WHEN** `Thickness` 大于 `0` 且 Depth Plane 发生置换
- **THEN** 侧壁包含位于正面与背面之间的细分环
- **AND** 每个环的位移按其从固定底面到可变面的归一化位置在零位移与完整位移之间线性插值
### Requirement: Depth displacement uses the calibrated zero plane
系统 SHALL 以采样 Z Depth 乘以隐藏的 `Uniform Scale` 得到局部深度,并以 `Base Plane Depth` 为零位移位置。比零平面更近的采样 SHALL 向正法线方向置换,比零平面更远的采样 SHALL 向固定底面置换。
#### Scenario: Sample the base depth
- **WHEN** 某个正面顶点采样的缩放深度等于 `Base Plane Depth`
- **THEN** 该顶点的深度位移为零
#### Scenario: Sample a nearer depth
- **WHEN** 某个正面顶点采样的缩放深度小于 `Base Plane Depth`
- **THEN** 该顶点沿正法线方向产生与 `Depth Scale` 成比例的位移
#### Scenario: Sample a farther depth
- **WHEN** 某个正面顶点采样的缩放深度大于 `Base Plane Depth`
- **THEN** 该顶点向固定底面产生与 `Depth Scale` 成比例的位移
### Requirement: Inward displacement does not cross the fixed base
系统 SHALL 把负向可变面位移限制在固定底面之前,并保留非零间隙以避免可变面、侧壁环和底面折叠或相交。正向位移 SHALL 不受 `Thickness` 限制,因此最终包围盒深度可以大于基础厚度。
#### Scenario: Excessive inward displacement
- **WHEN** 深度公式产生绝对值大于或等于 `Thickness` 的负向位移
- **THEN** 可变面停留在固定底面之前
- **AND** 所有侧壁环仍按顺序位于固定底面与可变面之间
#### Scenario: Outward displacement exceeds the base thickness
- **WHEN** 正向深度位移大于 `Thickness`
- **THEN** 系统保留完整正向位移
- **AND** 固定底面仍位于原图片平面

View File

@@ -0,0 +1,43 @@
## 1. Center Plane Origins
- [x] 1.1 Refactor the shared Plane mesh placement so centered local vertices and the compensated object matrix preserve the source Image Empty rectangle in world space.
- [x] 1.2 Apply the centered placement to Convert to Plane and Convert to Depth Plane without changing their UV range, selection replacement, names, or custom shape markers.
- [x] 1.3 Add Blender tests for offset Image Empty bounds and transformed sources, verifying centered origins and unchanged world-space corners for both conversion paths.
## 2. Unify Plane Material Ownership
- [x] 2.1 Remove the `Material` interface, Group Input, and Set Material node from `O Mesh Plane`, leaving generated faces on material index `0`.
- [x] 2.2 Remove the `Material` interface and forwarding from `O Image Depth Plane`, and simplify runtime Modifier input assignment while keeping image materials in Mesh slot `0`.
- [x] 2.3 Update Plane, thickness block, Depth Plane, and clipboard Plane validations to prove evaluated faces resolve the Mesh slot `0` material without a Modifier material input.
## 3. Rebuild Depth Plane Geometry
- [x] 3.1 Replace the single-extrude Depth Plane graph with the `O Mesh Plane` thickness topology positioned from the original image plane toward the positive-normal displaced face.
- [x] 3.2 Implement Z Depth sampling, `Base Plane Depth` zero calibration, `Depth Scale`, and linear layer weighting from the fixed base to the fully displaced face.
- [x] 3.3 Clamp inward displacement before the fixed base with a nonzero safety gap, preserve unrestricted outward displacement, and retain a single displaced surface when Thickness is zero.
- [x] 3.4 Ensure positive Thickness produces adaptive intermediate side-wall rings with a minimum useful ring count and stable UV/material indices.
## 4. Replace Interfaces and Defaults
- [x] 4.1 Replace `Depth Amount` with `Depth Scale` and `Reference Depth` with `Base Plane Depth` across the node builder, runtime Modifier inputs, validators, and tests without compatibility aliases.
- [x] 4.2 Set Convert to Depth Plane and its generated job handoff to default `Subdivide` to `6`, while keeping ordinary Plane default `0` and Metadata-derived hidden data unchanged.
- [x] 4.3 Add evaluated-geometry tests for zero Depth Scale thickness, fixed base vertices, zero-plane calibration, displacement direction and scale, side-ring interpolation, negative non-crossing, and outward displacement beyond the base thickness.
## 5. Rebuild Assets and Documentation
- [x] 5.1 Update node interface and layout validations, then rebuild `src/anyimage/assets/O_AnyImage.blend` with the final `O Mesh Plane` and `O Image Depth Plane` graphs.
- [x] 5.2 Update the Plane conversion and node asset internal documentation to describe centered origins, Mesh-slot material ownership, terrain-block topology, and the final control names.
- [x] 5.3 Run the node asset build pipeline and complete test suite, fixing any interface, topology, packaging, registration, or documentation consistency failures.
## 6. Correct Material Transfer and Thickness Anchor
- [x] 6.1 Preserve the source Mesh material set in generated `O Mesh Plane` geometry by joining a captured source marker, setting material index `0`, and deleting the source geometry.
- [x] 6.2 Anchor the fixed Depth Plane base at the original image plane so `Thickness` only moves the displaced face and intermediate side-wall rings.
- [x] 6.3 Update evaluated-geometry tests, OpenSpec artifacts, and documentation, then rebuild the node asset and run the complete validation suite.
## 7. Add Depth Plane Material Normal
- [x] 7.1 Make the Depth Plane Job request Z Depth and Tangent Normal from one shared `generate_moge2_artifacts()` call, returning `z_depth`, `depth_metadata`, and `tangent_normal` without a second inference.
- [x] 7.2 Load and Pack the Tangent Normal as Non-Color data, connect it to the Depth Plane image material in Tangent Space, and clean up unused Depth and Normal images on failure.
- [x] 7.3 Add server and Blender tests for the combined artifacts, material node connection, default `Subdivide` value, and failure cleanup; update documentation, rebuild the node asset, and run the complete validation suite.
- [x] 7.4 Set the Depth Plane image material Principled IOR to `1.2`, matching Cutout, and update its response test and documentation.