Add comprehensive tests for image processing and mask functionalities
- Introduced tests for color image handling, ensuring source preservation and premultiplied alpha usage. - Implemented tests for brush stroke operations, validating segment boundaries and brush anti-aliasing. - Added tests for frame anti-aliasing, confirming correct rendering behavior with various alpha values and depth settings. - Created tests for frame projection, ensuring accurate overlap calculations and half-plane clipping. - Developed tests for image premultiplied retention, verifying behavior during loading and packing processes. - Added undo functionality tests for mask operations, ensuring single transaction behavior during edits.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-03
|
||||
104
openspec/changes/standardize-cutout-z-axis/design.md
Normal file
104
openspec/changes/standardize-cutout-z-axis/design.md
Normal file
@@ -0,0 +1,104 @@
|
||||
## Context
|
||||
|
||||
Cutout 的二维采样和三角化本来在源 Image Empty 的局部 XY 平面完成,`cutout_domain()` 也先生成 `(x, y, 0)`。`build_base_shape()` 随后通过 `orient_cutout_vertices()` 将顶点变为 `(0, -x, y)`,让四个 Shape 子节点沿 X 轴建模;对象矩阵再根据 **Inward Axis** 选择是否追加一次轴变换。默认 `-Z` 只有在外层 `O Image Cutout` Modifier 求值后才回到源图坐标。
|
||||
|
||||
这使 Mesh 数据、Shape 子组和最终对象处于不同坐标系。禁用 Modifier、读取原始 Mesh、把 Shape 子组复用到普通 XY 几何,或依赖对象局部轴的后续操作,都会看到 YZ / X 轴约定。与此同时,`O Mesh Plane` 与 `O Image Depth Plane` 已经在 XY 平面生成几何并沿 Z 轴表达厚度和深度。
|
||||
|
||||
Depth Surface 的 Object Space Normal 也跟随旧 `-X` 原生协议:Server 先编码 X 轴 Cutout 空间,Blender 再为默认 `-Z` 修改图片像素。轴向迁移必须同时覆盖基础 Mesh、四个节点子组、外层节点组、对象矩阵与 Normal 编码,同时保留用户选择 `-Z` 或 `+X` 输出的能力。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 让 Cutout 原始 Mesh、四个 Shape 子组和默认 `-Z` 求值结果使用源 Image Empty 的局部 XY / Z 轴。
|
||||
- 让四种 Shape 的内向位移统一沿局部 `-Z`,与现有 Plane 节点的 Z 轴语义一致。
|
||||
- 保留 **Inward Axis** 输出选项,让默认 `-Z` 直通原生坐标,只在选择 `+X` 时执行配对的几何、对象矩阵和 Normal 转换。
|
||||
- 用 Python 单元测试、节点结构验证和真实 Blender 求值固定原始与求值几何的朝向。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不改变世界空间外观、Cutout 轮廓、UV、材质、厚度数值、Depth Scale 或 Reference Depth 的视觉语义。
|
||||
- 不修改 Selection、采样密度、三角化、Job 编排或 Depth Artifact。
|
||||
- 不删除或重命名现有 **Inward Axis** 接口和值,也不改变两种选项的世界空间外观。
|
||||
- 不重构已经采用 Z 轴的 `O Mesh Plane` 与 `O Image Depth Plane` 主链。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 保留三角化得到的 XY 顶点作为 BaseShape
|
||||
|
||||
`build_base_shape()` 直接保存 `cutout_domain()` 产生的 `(x, y, 0)`,删除 `orient_cutout_vertices()`。逆时针 Face 顺序保持不变,因此未求值基础面的几何法线为局部 `+Z`,从图片正面观察时与源 Image Empty 一致;“向图片内部”统一定义为局部 `-Z`。
|
||||
|
||||
默认 `-Z` 的对象矩阵为:
|
||||
|
||||
```text
|
||||
source_matrix × Translation(crop_center.x, crop_center.y, 0)
|
||||
```
|
||||
|
||||
默认路径不再追加轴变换。这样原始 Mesh 顶点、Modifier 输入和默认最终结果共享同一对象空间,世界空间仍由源图矩阵和裁切中心决定。选择 `+X` 时,外层节点组将 Z 轴 Shape 结果转换到 `+X` 内向坐标,对象矩阵追加其逆变换,使两次变换在世界空间抵消。
|
||||
|
||||
不选择只在对象创建后 Apply Modifier,因为这会丢失非破坏性 Shape 控制,也无法让子节点组直接复用 XY 输入几何。
|
||||
|
||||
### 2. 将四个 Shape 子组的建模方向整体迁移到 Z
|
||||
|
||||
节点构建脚本中所有表达厚度或轮廓外法向的 X 分量改为 Z 分量,平面内坐标由 Y/Z 改为 X/Y。相关辅助函数按职责改名,不保留 `_x` 转发函数。四个 Shape 使用同一约定:
|
||||
|
||||
```text
|
||||
image plane = XY
|
||||
front normal = +Z
|
||||
inward direction = -Z
|
||||
```
|
||||
|
||||
Surface 的前后层、Balloon 的轮廓高度、Depth Balloon 的相对深度和 Depth Surface 的相机点投影都在这一空间内直接产生原生坐标。外层 `O Image Cutout` 继续负责 **Inward Axis** Menu Switch:`-Z` 分支直通原生结果,`+X` 分支在 Cleanup、Smooth 后执行一次 Z 到 X 的坐标转换。
|
||||
|
||||
不选择保留内部 X 轴后仅提前旋转输入,因为子组单独复用时仍暴露旧约定,基础 Mesh 与节点 Field 的坐标也继续不一致。
|
||||
|
||||
### 3. Inward Axis 只转换外层输出
|
||||
|
||||
保留 Scene Property、工具面板项、交互参数、Operator Property、`CutoutInwardAxis` 与节点组 **Inward Axis** socket,枚举值仍为默认 `NEGATIVE_Z` 和可选 `POSITIVE_X`。语义调整为从统一的 Z 轴原生 Shape 选择输出对象空间:
|
||||
|
||||
```text
|
||||
-Z: geometry = native_z; object_matrix = source_matrix × crop_translation
|
||||
+X: geometry = Z_TO_X × native_z; object_matrix = source_matrix × crop_translation × X_TO_Z
|
||||
```
|
||||
|
||||
`Z_TO_X` 与 `X_TO_Z` 互为逆变换,因此两种选项仍产生相同的世界空间形状。`CUTOUT_AXIS_TO_SOURCE` 可保留并改成清楚表达 X 到 Z 的矩阵名称;不得让四个 Shape 子组依赖该矩阵或旧 X 轴输入。
|
||||
|
||||
不选择把选项下沉到各 Shape 子组,因为这会重新引入两套内部坐标,并让子组复用者必须理解项目专用的轴切换。
|
||||
|
||||
### 4. 在 Server 端编码原生 Z 轴 Object Space Normal
|
||||
|
||||
Depth Surface 继续请求 `normal_mode=OBJECT`,但 Object Space 输出直接采用原生 Z 轴 Cutout 空间的通道映射。中性/不可见区域编码为局部 `+Z`,与几何正面一致。默认 `-Z` 时 Blender 直接加载并 Pack 图片;选择 `+X` 时,`orient_object_normal_image()` 使用与几何 `Z_TO_X` 一致的法线矩阵转换通道。
|
||||
|
||||
Tangent Space Normal 的映射与其他 Shape 保持不变。虽然 Z 轴 Object Space 与 Tangent Space 在当前正交图片基面上可能具有相同的数值通道变换,两者仍保留不同的材质 Normal Space 语义与请求枚举。
|
||||
|
||||
不选择继续让 Artifact 固定使用旧 X 轴空间,因为默认路径仍需在 Blender 主线程复制和改写整张图片。迁移后只有显式 `+X` 选项承担这项转换成本。
|
||||
|
||||
### 5. 用三层验证覆盖轴向契约
|
||||
|
||||
- Python 几何测试检查 BaseShape 位于 `z = 0`、XY bounds 与源裁切一致、Face winding 产生 `+Z` 法线。
|
||||
- 节点结构验证检查四个子组只使用 Z 轴,外层组保留 **Inward Axis** 且仅 `+X` 分支包含最终轴变换。
|
||||
- Blender 求值测试以 XY Grid 直接驱动每个 Shape,检查平面内 X/Y 不被交换、Thickness/Depth 沿 `-Z`、默认原始 Mesh 与零形变求值结果朝向一致,并验证 `+X` 输出与对象矩阵组合后的世界空间结果不变。
|
||||
|
||||
已有 `O Mesh Plane` 与 `O Image Depth Plane` 只增加共同轴向断言,不改其实现。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [X 到 Z 的机械迁移可能遗漏某个位置、法线或边界分量] → 按四种 Shape 分别验证包围盒、法线、厚度和 Depth 求值,并搜索遗留的 X 轴辅助函数与常量。
|
||||
- [Face winding 与内向位移可能组合错误,造成背面或侧壁翻转] → 同时检查基础面、前后层和侧壁 Polygon Normal,而不只比较顶点包围盒。
|
||||
- [Depth Surface Object Normal 的通道映射可能与几何轴迁移不同步] → 用已知相机法线与不可见像素验证 Z 轴编码,并分别测试默认原样加载和 `+X` 单次转换。
|
||||
- [配对矩阵方向写反会让 `+X` 世界空间结果旋转] → 用非对称裁切与非单位源矩阵对比两种 Inward Axis 的世界空间顶点,而不只检查局部包围盒。
|
||||
- [重建资产可能引入无关二进制差异] → 仅修改 Cutout 构建路径,运行既有节点资产验证和完整测试,不构建其他文档或发布产物。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 先调整 BaseShape 与对象放置,补充未求值 Mesh 和世界空间位置测试。
|
||||
2. 将四个 Shape 子组从 X 轴迁移到 Z 轴,反转外层 `Inward Axis` 分支的直通/转换职责并更新结构、求值验证。
|
||||
3. 保留运行时 **Inward Axis** 属性与参数链,简化默认 `-Z` 对象矩阵并为 `+X` 使用配对补偿。
|
||||
4. 修改 Object Space Normal 为 Z 轴原生编码,让默认路径原样加载、`+X` 路径执行配对通道转换,并更新 Server/Blender 测试。
|
||||
5. 重建并验证 `O_AnyImage.blend`,运行 Cutout 定向测试和 `uv run pytest`,同步节点资产与 Cutout 内部文档。
|
||||
|
||||
回退时必须一起恢复节点构建脚本、`O_AnyImage.blend`、BaseShape 坐标、对象放置和 Normal 编码,避免资产与运行时使用不同坐标系。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。
|
||||
29
openspec/changes/standardize-cutout-z-axis/proposal.md
Normal file
29
openspec/changes/standardize-cutout-z-axis/proposal.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Why
|
||||
|
||||
Cutout 的基础 Mesh 目前先被转换到 YZ 平面,以局部 `-X` 作为正面法线,再由 `O Image Cutout` Modifier 转回源 Image Empty 的 XY 平面。未求值或未复用该 Modifier 时,基础面片朝向因此与原图不一致,也让 Cutout 与其他图片 Geometry Nodes 使用两套轴向约定。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 四种 Cutout Shape 统一在局部 XY 平面建模,以局部 `-Z` 表示从图片平面向内的方向;基础 Mesh、节点输入和默认 `-Z` 求值结果共享同一坐标约定。
|
||||
- Surface、Balloon、Depth Balloon 与 Depth Surface 中所有厚度、鼓起、深度投影和相关向量运算由 X 轴迁移到 Z 轴。
|
||||
- 保留 Cutout 工具与 `O Image Cutout` 的 **Inward Axis** 选项;`-Z` 继续作为默认输出,选择 `+X` 时由外层节点组和对象矩阵从原生 Z 轴结果执行配对转换。
|
||||
- Depth Surface 的 Object Space Normal 默认按 Z 轴对象空间生成;仅在选择 `+X` 输出时转换到对应对象空间。
|
||||
- **BREAKING(Shape 子节点内部坐标)**:`O Image Surface`、`O Image Balloon`、`O Image Depth Balloon` 与 `O Image Depth Surface` 的原生输入和输出从 X 轴约定改为 Z 轴约定,不提供子组级旧坐标兼容层。
|
||||
- 重建并验证节点资产,确保 Cutout 与既有 `O Mesh Plane`、`O Image Depth Plane` 均遵循 Z 轴图片几何约定。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `cutout-axis-convention`: 规定 Cutout 基础 Mesh与四种 Shape Geometry Nodes 的原生局部 Z 轴语义,以及 `Inward Axis` 对输出几何、对象放置和 Object Space Normal 的配对转换。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响 Cutout BaseShape 坐标、对象创建与放置,以及既有 `Inward Axis` 参数的节点转换方向。
|
||||
- 影响 `O Image Cutout` 及四个 Shape 子节点组的构建脚本、内部坐标约定、验证脚本与发布资产 `O_AnyImage.blend`;外层 `Inward Axis` 接口和值保持不变。
|
||||
- 影响 Depth Surface Object Space Normal 的坐标转换及相关 Server、Blender 和真实节点求值测试。
|
||||
- 不改变 Cutout 的选区、网格密度、UV、材质、Job 请求或 Depth/Normal 文件格式,也不新增依赖。
|
||||
@@ -0,0 +1,61 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Cutout base mesh matches the source image plane
|
||||
系统 SHALL 直接在源 Image Empty 的局部 XY 平面创建 Cutout BaseShape,基础顶点 MUST 使用 `z = 0`,逆时针正面 MUST 朝局部 `+Z`,且对象放置 MUST 保持裁切区域的世界空间位置。
|
||||
|
||||
#### Scenario: Inspect a Cutout before Geometry Nodes evaluation
|
||||
- **WHEN** 系统已从源图片裁切区域创建 Cutout Mesh,但尚未求值或复用 `O Image Cutout` Modifier
|
||||
- **THEN** Mesh 位于对象局部 XY 平面,正面朝向与源 Image Empty 一致,且 X/Y 范围对应原裁切区域
|
||||
|
||||
#### Scenario: Evaluate an undeformed Surface
|
||||
- **WHEN** Surface Cutout 以零厚度和零平滑求值
|
||||
- **THEN** 求值前后的平面内坐标、正面朝向和世界空间位置保持一致,不执行轴交换或补偿旋转
|
||||
|
||||
### Requirement: Cutout Shape nodes use the local Z axis
|
||||
`O Image Surface`、`O Image Balloon`、`O Image Depth Balloon` 与 `O Image Depth Surface` SHALL 接收局部 XY 平面几何,并统一使用局部 `-Z` 表示从图片平面向内的方向。外层 `O Image Cutout` 的默认 `-Z` 分支 MUST 直通该原生结果。平面内 X/Y 坐标 MUST 不因 Shape 求值而互换。
|
||||
|
||||
#### Scenario: Reuse a Shape group with XY geometry
|
||||
- **WHEN** 任一 Cutout Shape 节点组直接接收位于 XY 平面的输入 Mesh
|
||||
- **THEN** 节点组沿 Z 轴生成 Shape,并保持输入几何的 X/Y 方向
|
||||
|
||||
#### Scenario: Build Surface or Balloon thickness
|
||||
- **WHEN** Surface 或 Balloon 使用正 Thickness 求值
|
||||
- **THEN** 厚度和 Balloon 高度沿局部 Z 轴展开,图片内部一侧位于负 Z 方向
|
||||
|
||||
#### Scenario: Apply depth deformation
|
||||
- **WHEN** Depth Balloon 或 Depth Surface 使用非零 Depth Scale 求值
|
||||
- **THEN** 深度位置在统一的 XY / Z 对象空间中计算,不依赖 X 轴中间坐标系或最终轴变换
|
||||
|
||||
### Requirement: Cutout exposes selectable output axes
|
||||
系统 SHALL 保留 **Inward Axis** Scene Property、Operator 参数和 `O Image Cutout` 节点接口。`-Z` MUST 为默认值并直接使用原生 XY / Z Shape 结果;选择 `+X` 时,外层节点组 MUST 将 Z 轴结果转换为 `+X` 内向对象空间,对象矩阵 MUST 执行配对补偿以保持相同世界空间形状。
|
||||
|
||||
#### Scenario: Create a Cutout from the tool
|
||||
- **WHEN** 用户打开 Cutout 工具设置并选择任一 Shape
|
||||
- **THEN** 工具显示并传递 **Inward Axis**,默认 `-Z` 创建结果直接沿用源 Image Empty 的局部轴
|
||||
|
||||
#### Scenario: Select the positive X output
|
||||
- **WHEN** 用户把 **Inward Axis** 设为 `+X`
|
||||
- **THEN** `O Image Cutout` 将原生 Z 轴 Shape 转换为 `+X` 内向的局部几何,对象矩阵同步补偿,世界空间位置和外观与默认结果一致
|
||||
|
||||
#### Scenario: Reuse a Shape subgroup directly
|
||||
- **WHEN** 调用方绕过外层 `O Image Cutout` 并直接复用任一 Shape 子节点组
|
||||
- **THEN** 子节点组始终使用原生 XY / Z 坐标,不公开或模拟旧 X 轴内部约定
|
||||
|
||||
### Requirement: Depth Surface normals match the Z-axis object space
|
||||
Depth Surface 的 Object Space Normal Artifact SHALL 编码原生 Z 轴 Cutout 对象空间;不可见区域 MUST 使用局部 `+Z` 中性法线。默认 `-Z` 输出 MUST 原样使用该图片;选择 `+X` 输出时,Blender MUST 将其转换到配对的 `+X` 对象空间。
|
||||
|
||||
#### Scenario: Encode a known object-space normal
|
||||
- **WHEN** Server 为 Depth Surface 编码已知的相机空间法线
|
||||
- **THEN** 输出通道直接对应最终 XY / Z Cutout 对象空间,且无需 Blender 后处理即可作为 Object Space Normal 使用
|
||||
|
||||
#### Scenario: Load a Depth Surface normal image
|
||||
- **WHEN** Blender 收到 `object-normal.png` 并以默认 `-Z` 创建 Depth Surface 材质
|
||||
- **THEN** 图片像素保持 Artifact 原值并以 Object Space 连接到材质
|
||||
|
||||
#### Scenario: Load a positive X Depth Surface normal image
|
||||
- **WHEN** Blender 收到 `object-normal.png` 并以 `+X` 创建 Depth Surface 材质
|
||||
- **THEN** 图片通道按 Z 到 X 的配对轴变换转换一次,再以 Object Space 连接到材质
|
||||
|
||||
#### Scenario: Fill an invisible normal region
|
||||
- **WHEN** Object Space Normal 的像素不在可见内容区域
|
||||
- **THEN** 该像素编码局部 `+Z`,不会在正面产生虚假的侧向法线
|
||||
30
openspec/changes/standardize-cutout-z-axis/tasks.md
Normal file
30
openspec/changes/standardize-cutout-z-axis/tasks.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 1. 固定轴向契约
|
||||
|
||||
- [x] 1.1 补充 BaseShape 与对象放置测试,验证原始 Mesh 位于局部 XY、正面为 `+Z`、裁切区域世界空间位置不变
|
||||
- [x] 1.2 补充 Cutout 属性、交互和对象测试,验证 `Inward Axis` 继续显示和传递,且 `-Z` 为默认值
|
||||
- [x] 1.3 扩展节点结构与真实 Blender 求值测试,分别固定四种 Shape 的 XY 输入、Z 轴形变、Face winding,以及外层 `-Z`/`+X` 配对转换
|
||||
- [x] 1.4 补充 Object Space Normal 的 Server 编码与 Blender 加载测试,验证 `+Z` 中性值、默认原样加载和 `+X` 单次通道转换
|
||||
|
||||
## 2. 统一 BaseShape 与对象坐标
|
||||
|
||||
- [x] 2.1 让 `build_base_shape()` 直接保留三角化的 `(x, y, 0)` 顶点,删除 X 轴顶点转换及其遗留测试
|
||||
- [x] 2.2 让默认 `-Z` 对象矩阵只使用源矩阵与裁切中心平移,并为 `+X` 保留从原生 Z 轴到 X 轴输出的配对矩阵补偿
|
||||
- [x] 2.3 保持 Scene Settings、工具面板、交互编码、Operator Property 和对象创建调用链的 `Inward Axis` 接口,清理只服务旧 X 轴内部默认的逻辑
|
||||
|
||||
## 3. 迁移 Cutout Geometry Nodes
|
||||
|
||||
- [x] 3.1 将 Cutout 节点构建辅助函数从 X 轴厚度/偏移改为 Z 轴,并按最终职责重命名,不保留旧函数转发
|
||||
- [x] 3.2 将 Surface、Balloon、Depth Balloon 与 Depth Surface 的平面坐标、厚度、深度投影和向量运算统一迁移到 XY / Z 坐标
|
||||
- [x] 3.3 保留 `O Image Cutout` 的 `Inward Axis` socket 与 Menu Switch,让 `-Z` 直通原生 Shape、`+X` 执行 Z 到 X 的最终 Transform,并更新节点布局验证
|
||||
- [x] 3.4 更新 Cutout 与既有 Plane 的节点资产验证,重建并验证 `src/anyimage/assets/O_AnyImage.blend`
|
||||
|
||||
## 4. 对齐 Object Space Normal
|
||||
|
||||
- [x] 4.1 修改 Server 的 Object Space Normal 通道映射和不可见像素值,使 Artifact 直接使用最终 XY / Z Cutout 对象空间
|
||||
- [x] 4.2 调整 Blender 端 Object Space Normal 转换方向:默认 `-Z` 原样消费 Artifact,仅 `+X` 在响应阶段执行一次 Z 到 X 的通道变换
|
||||
|
||||
## 5. 文档与完整验证
|
||||
|
||||
- [x] 5.1 更新 Cutout Tool 与节点资产内部文档,用从上到下的流程说明统一 XY / Z 坐标和四种 Shape 的 `-Z` 内向语义
|
||||
- [x] 5.2 搜索并清理 Shape 子组以 X 轴为内部默认的实现和说明,同时确认 `Inward Axis`、`CutoutInwardAxis` 与外层转换只承担可选输出轴职责
|
||||
- [x] 5.3 运行 Cutout、Server、Blender 与节点资产定向测试,再运行 `uv run pytest`;仅修复本 change 引入的回归,不构建其他文档或发布产物
|
||||
Reference in New Issue
Block a user