Refactor Cutout and Image Editing Logic
- Renamed "Base Plane Depth" to "Reference Depth" across validate_cutout.py for consistency. - Introduced a new specification for Cutout mesh sampling to ensure source pixel spacing is preserved and limits are applied correctly. - Unified naming conventions for image editing results to maintain consistency across Remove Background, Upscale, and Crop operations. - Updated the handling of Refine Selection to ensure it retains the original bounds and does not apply unnecessary cropping. - Enhanced material generation logic to connect image color directly to IOR Level, improving the rendering of dark areas. - Established clear requirements for geometry depth controls and image result materials to ensure consistent behavior across different components. - Removed outdated logic and ensured all changes are documented and tested for regression.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-02
|
||||
@@ -0,0 +1,69 @@
|
||||
## Context
|
||||
|
||||
Cutout 交互当前先把 Mesh Detail 换算为整张源图的 `mesh_resolution`,随后又把 Selection bounds 交给通用图片尺寸限制函数。后一步比较的是 bounds 原始像素数而不是预期网格采样数,因此在 Low 的 32 px 间距下,2048 px bounds 会被错误地当作超过默认 1024 网格上限并再次减半。
|
||||
|
||||
Image Empty 编辑已经共用替换与共享引用分流,但结果产生仍有两条旧路径:Job 结果使用面向材质的 Color 命名,本地像素结果使用去扩展名的基础名。替换逻辑只在旧 Image 可以被删除时把结果改回源名,所以共享引用会暴露两条路径的不一致。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 在 Cutout 全链路保留 Mesh Detail 的源像素间距语义,只对实际几何采样数量应用性能上限。
|
||||
- 为直接 Image 编辑建立单一结果命名边界,使本地像素结果和 Job 文件结果一致。
|
||||
- 明确分开 Image 编辑结果与材质派生贴图,删除错误的旧创建入口和调用方中的隐式命名修补。
|
||||
- 使用真实 Blender 数据块测试覆盖共享与非共享源 Image。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不修改 BEN2、Upscale、Crop 的像素内容、文件编码、动画帧或 Server 协议。
|
||||
- 不改变 Plane、Depth Plane、Cutout Shape 的材质贴图后缀。
|
||||
- 不自行生成或解析 Blender 的 `.001` 数字唯一性后缀。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 保留像素间距,延后计算网格上限
|
||||
|
||||
Mesh Detail 在交互边界只产生 32、16、8、4 px 的采样间距,不再提前压缩为受 1024 限制的整图分辨率。Selection bounds 已知后,使用 `bounds 长边 ÷ 像素间距` 得到实际 Cutout 的预期采样长边;只有该值超过配置上限时才等比增大有效间距。几何层再根据源 Image 尺寸与 Image Empty 的世界尺度把有效像素间距换算为世界空间采样间距。
|
||||
|
||||
这比修补现有 `mesh_resolution × limited_image_size(bounds)` 公式更直接,因为配置项限制的是网格采样数量,不是可送入图片处理器的像素尺寸。也不采用简单 `min(整图 mesh_resolution, 上限)`,因为小 Selection 位于超大源图时仍应保持所选档位的像素间距。
|
||||
|
||||
### 2. 直接 Image 编辑结果始终请求完整源名
|
||||
|
||||
在 `common/image.py` 建立明确的 Image 编辑结果创建与加载入口。入口接收源 Image,并在创建或加载后把结果名称设置为 `source_image.name` 原值;旧 Image 尚存在时产生的数字后缀完全交给 Blender。`replace_empty_image()` 继续负责共享引用分流,并在旧 Image 可删除时把结果恢复为准确源名。
|
||||
|
||||
Remove Background 与 Upscale 改用 Image 编辑 Job 结果加载入口;三种 Selection Crop、本地 Crop、Refine Crop 和 Perspective Crop 改用同一像素结果创建入口。中间 BEN2 编码图不属于用户结果,可以继续使用临时名称并在响应结束时删除。
|
||||
|
||||
不采用在各 Operator 响应中分别写 `result_image.name = source.name`,因为这会继续保留重复逻辑,也无法保证本地 Crop 与异步 Job 的行为一致。
|
||||
|
||||
### 3. 材质派生贴图保留独立入口
|
||||
|
||||
`color_data_name()`、`depth_data_name()`、`normal_data_name()` 以及材质 Color Job 结果加载只服务 Plane、Depth Plane、Cutout Shape 等派生贴图工作流。若底层加载、Pack、颜色空间与序列设置需要复用,则由公共内部过程接收显式目标名;面向业务的两个公开入口仍分别表达“编辑原 Image”和“生成 Color 贴图”。
|
||||
|
||||
不把 `load_color_result_image()` 全局改为源名,因为 Convert to Plane 等调用方确实需要 `_color.png`,全局改名会把本次修复扩散为材质契约变化。
|
||||
|
||||
### 4. 删除错误的旧 Image 添加路径
|
||||
|
||||
迁移完成后直接删除旧的 Image 添加逻辑:面向 Crop 的旧基础名创建入口不保留默认或可选 `suffix` 形式,纯 Image 编辑也不再允许调用材质 Color 结果入口。同步清理 `image_base_name()` 等仅为旧路径存在的调用方导入、重复赋名和失去调用者的 helper;不添加别名、转发函数或弃用兼容层。
|
||||
|
||||
这比保留一个可同时表达“源图编辑”和“派生贴图”的宽泛函数更容易验证,也符合项目一次性迁移、不保留旧路径的约束。
|
||||
|
||||
### 5. 测试按可观察的数据块结果组织
|
||||
|
||||
纯函数测试验证 Mesh Detail 间距、bounds 实际采样数与上限;Blender 测试以同一 Image 被两个 Empty 引用和单一引用两种场景验证结果数据、名称与旧引用。Operator 测试分别覆盖 Remove Background、Upscale、Selection Crop(本地与 Refine)和 Perspective Crop 都调用统一入口,材质结果测试则确认 `_color` 等名称不变。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [高分辨率小 Selection 不再被整图 1024 上限提前降密,局部网格可能比当前更多] → 仍按实际 Cutout 采样长边应用配置上限,并加入边界测试。
|
||||
- [浮点 bounds 与取整可能令采样数相差一个点] → 对上限计算统一取整规则,规格只约束不超过上限和比例密度,不依赖具体三角形数量。
|
||||
- [动画 Job 结果的 Image 名称可能保留 `.mp4` 等源名称文字] → 名称被定义为数据块身份而非结果文件格式;实际序列来源、Pack 和帧设置继续由加载入口配置。
|
||||
- [重构公共加载入口误改材质 Color 命名] → 用独立公开入口和回归测试锁定 Image 编辑与材质贴图两种语义。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 先补充失败测试,分别重现 Upscale 后 Cutout 密度被抵消、共享 Image 出现 `_color` 和丢扩展名。
|
||||
2. 调整 Cutout 参数传递与实际采样上限计算,移除把 bounds 当图片输入限制的路径。
|
||||
3. 收拢 Image 编辑结果创建、加载和命名入口,迁移六类编辑工具调用方,同时保留材质贴图入口。
|
||||
4. 删除错误的旧 Image 添加入口、参数、导入和调用方命名修补,不保留兼容转发。
|
||||
5. 更新 Common、Crop Tool、Cutout Tool 文档并运行定向测试和全量测试。
|
||||
|
||||
本次只改变新执行结果,不迁移现有 `.blend` 中已经创建的数据块。若回退,代码可整体恢复;已有结果数据不需要转换。
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
Cutout 的 Mesh Detail 先按源图像素间距计算,却又按 Selection bounds 的原始像素尺寸缩小网格分辨率,导致 Upscale 后的新增像素无法转化为对应的网格细节。Remove Background、Upscale 与 Crop 同属直接编辑 Image Empty 的工作流,但共享 Image 数据时分别出现 `_color` 后缀或丢失 `.png` 扩展名,说明结果创建仍混用了材质贴图命名与旧的基础名逻辑。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 让 Cutout 的 Low、Medium、High、Ultra 始终分别保持 32、16、8、4 个源图像素的采样间距;Upscale x4 后,同一图像范围在线性方向获得约 4 倍采样密度,直到实际网格采样数量触及配置上限。
|
||||
- 将 **Maximum Cutout Mesh Resolution** 只应用于实际网格采样长边,不再把 Selection bounds 的原始像素长边当作待缩放的图片输入尺寸。
|
||||
- 统一 Remove Background、Upscale、Crop Box、Crop Lasso、Crop Polyline 与 Crop Perspective 的纯 Image 编辑结果命名:始终以源 Image 数据块的完整名称请求新名称,保留 `.png` 等原有格式文字,不主动添加 `_color` 等材质贴图后缀。
|
||||
- 允许 Blender 在源 Image 仍被共享时自动追加 `.001` 等唯一性后缀;代码不自行模拟、剥离或改写该后缀。
|
||||
- 收拢纯 Image 编辑结果的创建、加载、命名与替换入口,使材质 Color、Depth、Normal 的派生贴图命名只留在 Plane、Cutout 等材质工作流。
|
||||
- 删除原有错误的 Image 添加逻辑、旧入口和调用方修补,不保留可选后缀参数、转发函数或兼容层。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `cutout-mesh-sampling`: 规定 Cutout Mesh Detail 的源像素采样间距、Upscale 后的密度变化及实际网格分辨率上限。
|
||||
- `image-edit-results`: 规定直接编辑 Image Empty 的结果数据块命名、共享引用分流以及与材质贴图命名的边界。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。
|
||||
|
||||
## Impact
|
||||
|
||||
- 影响 `operators/cutout_tool` 的网格分辨率计算及对应几何、交互测试。
|
||||
- 影响 `common/image.py` 中 Image 编辑结果的创建、加载、命名和 Empty 替换职责,以及 Remove Background、Upscale、Crop Selection、Refine Crop、Crop Perspective 的调用路径;旧的错误创建入口及无用导入会被移除。
|
||||
- Plane、Depth Plane、Cutout Shape 使用的 `_color.png`、`_depth.exr`、`_normal.png` 材质贴图命名保持不变。
|
||||
- 需要同步 Common、Crop Tool、Cutout Tool 与相关运行时文档;不新增依赖或 Server 协议。
|
||||
@@ -0,0 +1,28 @@
|
||||
## Purpose
|
||||
|
||||
规定 Cutout 网格细节与源图像素之间的稳定关系,并让性能上限约束实际生成的网格采样数量而不是输入图片尺寸。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Mesh Detail preserves source-pixel spacing
|
||||
系统 SHALL 将 Cutout 的 Low、Medium、High、Ultra 分别解释为相邻网格采样点之间 32、16、8、4 个源图像素,并在未触及网格上限时保持该间距。
|
||||
|
||||
#### Scenario: Low samples every 32 source pixels
|
||||
- **WHEN** 用户以 Low 创建一个未触及网格上限的 Cutout
|
||||
- **THEN** 系统按每 32 个源图像素一次的间距构建网格
|
||||
|
||||
#### Scenario: Upscale increases linear mesh density
|
||||
- **WHEN** 同一图片内容在线性宽高各 Upscale 4 倍后,以相同 Mesh Detail、相同相对 Selection 和相同显示尺寸创建 Cutout,且两次均未触及网格上限
|
||||
- **THEN** Upscale 结果在同一线性内容范围内获得约 4 倍网格采样密度
|
||||
|
||||
### Requirement: Cutout mesh limit caps actual sampling resolution
|
||||
系统 SHALL 仅在 Cutout 实际几何范围的预期采样长边超过 **Maximum Cutout Mesh Resolution** 时增大采样间距,并 SHALL NOT 按 Selection bounds 的原始像素长边直接缩放已经由 Mesh Detail 得到的密度。
|
||||
|
||||
#### Scenario: Large pixel bounds remain below the sampling limit
|
||||
- **WHEN** Selection bounds 的像素长边超过配置值,但按当前 Mesh Detail 计算的实际采样长边没有超过配置值
|
||||
- **THEN** 系统保持该 Mesh Detail 对应的源像素采样间距
|
||||
|
||||
#### Scenario: Actual sampling reaches the configured limit
|
||||
- **WHEN** Cutout 实际几何范围按当前 Mesh Detail 计算的采样长边超过配置值
|
||||
- **THEN** 系统等比增大网格采样间距,使实际采样长边不超过配置值
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
## Purpose
|
||||
|
||||
规定直接编辑 Image Empty 时结果 Image 数据块的统一身份与命名,使共享引用只由 Blender 的数据块唯一性规则产生差异。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Direct image edits preserve the complete source name
|
||||
Remove Background、Upscale、Crop Box、Crop Lasso、Crop Polyline 和 Crop Perspective SHALL 使用源 Image 数据块的完整名称请求结果 Image 名称,包括名称中的 `.png` 等格式文字,并 SHALL NOT 主动添加材质贴图后缀。
|
||||
|
||||
#### Scenario: Remove Background edits a shared image
|
||||
- **WHEN** Remove Background 编辑一个仍被其他对象引用的 Image
|
||||
- **THEN** 新结果以源 Image 的完整名称请求命名,唯一性数字后缀仅由 Blender 自动追加
|
||||
|
||||
#### Scenario: Upscale edits a shared image
|
||||
- **WHEN** Upscale 编辑一个仍被其他对象引用的 Image
|
||||
- **THEN** 新结果保留源 Image 的完整名称且不添加 `_color`
|
||||
|
||||
#### Scenario: Selection Crop edits a shared image
|
||||
- **WHEN** 任一 Selection Crop 路径编辑一个仍被其他对象引用的 Image,无论是否启用 Refine Selection 或 Keep Original
|
||||
- **THEN** 新结果保留源 Image 的完整名称且不移除名称中的格式文字
|
||||
|
||||
#### Scenario: Perspective Crop edits a shared image
|
||||
- **WHEN** Crop Perspective 编辑一个仍被其他对象引用的 Image,无论是否启用 Keep Original
|
||||
- **THEN** 新结果保留源 Image 的完整名称且不移除名称中的格式文字
|
||||
|
||||
### Requirement: Image replacement follows Blender data-block identity
|
||||
系统 SHALL 在源 Image 只由被编辑对象使用时以结果替换并清理旧数据,使结果最终恢复源 Image 的准确名称;源 Image 仍被共享或被 Keep Original 保留时,系统 SHALL 保留源数据并接受 Blender 自动生成的数字唯一性后缀。
|
||||
|
||||
#### Scenario: Unshared source is replaced
|
||||
- **WHEN** 直接图片编辑替换一个没有其他用户且没有 Fake User 的源 Image
|
||||
- **THEN** 旧 Image 被清理且结果 Image 最终使用源 Image 的准确名称
|
||||
|
||||
#### Scenario: Shared source is split
|
||||
- **WHEN** 直接图片编辑替换一个仍有其他用户的源 Image
|
||||
- **THEN** 其他用户继续引用源 Image,被编辑对象引用新结果,两个数据块的重名冲突由 Blender 自动解决
|
||||
|
||||
### Requirement: Material texture naming remains separate
|
||||
系统 SHALL 仅对 Plane、Depth Plane、Cutout Shape 等生成材质贴图的工作流使用 `_color.png`、`_depth.exr`、`_normal.png` 等业务后缀,直接图片编辑 SHALL NOT 复用这些派生贴图名称。
|
||||
|
||||
#### Scenario: Plane conversion creates material textures
|
||||
- **WHEN** 图片被转换为需要材质贴图的 Plane 或 Cutout 结果
|
||||
- **THEN** 其 Color、Depth 和 Normal 数据继续按对应材质贴图规则命名
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
## 1. 锁定回归行为
|
||||
|
||||
- [x] 1.1 为 Cutout 增加按源像素间距和实际 Selection 采样长边计算的测试,覆盖 Low 的 32 px、Upscale x4 的 4 倍线性密度、bounds 像素超过上限但采样数未超限、实际采样数触及上限,并运行 `uv run pytest tests/test_cutout_interaction.py tests/test_cutout_geometry.py`
|
||||
- [x] 1.2 为 Image 编辑结果增加真实 Blender 数据块测试,覆盖完整 `.png` 名称、共享源或 Keep Original 产生的 Blender 数字后缀、非共享源恢复准确原名,并运行对应 `tests/test_cutout_objects.py` 用例
|
||||
- [x] 1.3 为 Remove Background、Upscale、Selection Crop 本地与 Refine 路径、Crop Perspective 增加调用边界测试,确认它们统一请求 Image 编辑结果而不使用 `_color` 或基础名,并运行对应 `tests/test_blender_addon.py` 用例
|
||||
|
||||
## 2. 修正 Cutout 网格采样
|
||||
|
||||
- [x] 2.1 将 Mesh Detail 的 32、16、8、4 px 间距直接保留到 Cutout 执行阶段,移除交互阶段受整图 1024 限制的提前换算,并运行 Cutout 交互测试验证四档参数
|
||||
- [x] 2.2 根据 Selection bounds 的预期网格采样长边应用 **Maximum Cutout Mesh Resolution**,把最终有效像素间距换算为世界空间 spacing,删除复用图片输入尺寸限制的错误路径,并运行 Cutout 几何测试验证密度比例和上限
|
||||
- [x] 2.3 更新同步创建与异步 AI 响应的参数传递,使四种 Cutout Shape 使用同一有效间距,并运行 `uv run pytest tests/test_cutout_interaction.py tests/test_cutout_geometry.py tests/test_cutout_objects.py tests/test_blender_addon.py`
|
||||
|
||||
## 3. 统一 Image 编辑结果命名
|
||||
|
||||
- [x] 3.1 在 `common/image.py` 建立分别面向像素编辑结果和 Job 编辑结果的共享入口,两者都请求源 Image 完整名称并复用 Pack、颜色空间、动画与错误清理;保留独立材质 Color 加载入口,并运行 Image 数据测试
|
||||
- [x] 3.2 将 Remove Background 与 Upscale 迁移到共享 Job 编辑结果入口,删除调用方命名修补,并运行对应 Operator 响应测试验证共享与非共享 Image
|
||||
- [x] 3.3 将 Selection Crop、Refine Crop 和 Crop Perspective 迁移到共享像素编辑结果入口,移除默认 `image_base_name()` 命名,并运行本地、Refine、Perspective 与 Keep Original 测试
|
||||
- [x] 3.4 删除旧的错误 Image 添加入口、可选后缀参数、重复赋名、无用导入与失去调用者的 helper,不保留别名、转发函数或兼容层,并用 `rg` 确认纯 Image 编辑 Operator 只调用新的共享入口
|
||||
- [x] 3.5 补充 Plane、Depth Plane 与 Cutout Shape 的材质命名回归断言,确认 `_color.png`、`_depth.exr`、`_normal.png` 不受 Image 编辑结果重构影响,并运行 `uv run pytest tests/test_image_data.py tests/test_cutout_objects.py tests/test_blender_addon.py`
|
||||
|
||||
## 4. 文档与完整验证
|
||||
|
||||
- [x] 4.1 更新 `docs/internals/common.md`、Crop Tool、Cutout Tool 与相关运行时说明,正面描述 Image 编辑结果完整源名、Blender 自动数字后缀和实际 Cutout 采样上限,并用 `rg` 核对代码与文档术语一致
|
||||
- [x] 4.2 运行 `uv run pytest`,确认全部测试通过且没有恢复旧命名入口、兼容层或图片尺寸限制胶水代码
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-09-02
|
||||
98
openspec/changes/refine-image-output-semantics/design.md
Normal file
98
openspec/changes/refine-image-output-semantics/design.md
Normal file
@@ -0,0 +1,98 @@
|
||||
## Context
|
||||
|
||||
Plane、Depth Plane 与 Cutout 的非 Shadeless 材质都由 `common/material.py` 创建,但 `O Image Layer` 的颜色当前只驱动 Base Color、Subsurface Radius、Emission Color 与 Roughness,Principled BSDF 的 IOR Level 保持常量。Depth Plane 与两个 Depth Cutout 子组把相机参考深度公开为 `Base Plane Depth`,而实现和元数据已经统一使用 `reference_depth`。
|
||||
|
||||
Refine Selection 当前把 Selection bounds 内的矩形 RGBA 交给 BEN2,并由 `preserve_input_alpha=True` 得到 `Source Alpha × BEN2 Alpha`,随后 Server 会按可见 Alpha 紧裁切并返回新的局部 bounds。Cutout 在启用 Refine 后直接用 BEN2 Alpha 构建网格,丢失了用户的 Selection Mask;Crop 也直接采用 BEN2 输出。与此同时,Cutout 的 MoGe-2、Normal 与 Depth 需要完整的 Refine 内容范围,不应被 Selection Mask 限制。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 让生成材质的暗色区域通过 IOR Level 降低高亮。
|
||||
- 将 Geometry Nodes 的参考深度接口统一为与实现一致的 `Reference Depth`。
|
||||
- 固定 Refine 输入与输出的矩形尺寸和 placement bounds。
|
||||
- 让 Cutout 的 Color、Normal 与 Depth 共享 `Source Alpha × BEN2 Alpha` 内容范围。
|
||||
- 让 Selection Mask 只约束最终 Crop 图片或 Cutout 网格。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不改变 BEN2、MoGe-2、Normal 或 Depth 模型实现和推理分辨率。
|
||||
- 不用 Selection Mask 限制 BEN2、MoGe-2 或任何 Cutout 贴图的生成范围。
|
||||
- 不改变 Depth EXR 的连续 RGB Field 或 MoGe-2 Validity Alpha 语义。
|
||||
- 不为旧 Geometry Nodes socket 名称、旧 Job 结果 key 或旧节点资产保留兼容层。
|
||||
- 不改变普通本地 Crop、普通本地 Cutout 或 Shadeless 材质。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. `O Image Layer` Color 直接驱动 IOR Level
|
||||
|
||||
在 `create_image_material()` 的 Principled 分支中,将 `O Image Layer` 的 Color 输出直接连接到 Principled BSDF 的 IOR Level。Blender 按 Color-to-Float 规则完成转换,因此黑色得到 `0`、白色得到 `1`;既有 IOR 数值继续独立控制 Fresnel 模型。该公共入口只被 Plane、Depth Plane 与 Cutout 使用,因此无需新增调用参数或材质节点组 socket。
|
||||
|
||||
相比新增亮度 Math 节点或只降低默认值,直接连接准确表达颜色层驱动关系,节点更少,也让暗色抑制与原图连续变化。
|
||||
|
||||
### 2. 公开接口统一使用 `Reference Depth`
|
||||
|
||||
将 `O Image Depth Plane`、`O Image Cutout`、Depth Balloon 与 Depth Surface 的 `Base Plane Depth` socket 全部改名为 `Reference Depth`。Modifier 初始化与节点间连线按新名称直接迁移,内部 `reference_depth` 变量和 `depth.json` 字段保持不变。
|
||||
|
||||
相比 `Base Depth`,`Reference Depth` 更准确说明该值是采样深度的运算基准,并与现有代码和元数据术语一致。节点资产随构建脚本重新生成,不保留旧 socket。
|
||||
|
||||
### 3. Refine 输出保持提交前的完整矩形画布
|
||||
|
||||
`server/selection/refine_selection.py` 只运行 BEN2 并保存与输入相同尺寸的 RGBA,不再按 Alpha 调用紧裁切,也不返回局部 bounds。`refine-image-selection` 和 Cutout artifact Job 删除 `selection_bounds` 产物;Blender 调用方继续使用提交前保存的 Selection bounds 进行 placement。
|
||||
|
||||
BEN2 继续使用 `preserve_input_alpha=True`,因此统一 Refine 内容 Alpha 定义为:
|
||||
|
||||
```text
|
||||
refined_alpha = source_alpha × ben2_alpha
|
||||
```
|
||||
|
||||
Server 不接收 SelectionPath、SelectionMask 或 Mask sidecar。相比在 Server 组合 Selection Mask,稳定矩形输出让同一 Refine 结果可以直接服务 Color、Normal 与 Depth,并保持 AI 与用户几何约束分离。
|
||||
|
||||
### 4. Cutout 贴图共享 Refine 内容,网格再乘 Selection Mask
|
||||
|
||||
启用 Refine 时,Cutout artifact Job 将完整 BEN2 RGBA 同时作为最终 Color 和 MoGe-2 输入。Normal 的可见区域、Depth Metadata 的参考内容区域,以及 MoGe-2 黑底合成均继续从该图片 Alpha 派生。Depth EXR RGB 仍保存完整连续 Field,Alpha 仍只保存 `GeometryFrame.validity`。
|
||||
|
||||
Blender 响应阶段读取完整 BEN2 Alpha,并与提交时已栅格化的 `SelectionMask.values` 逐像素相乘:
|
||||
|
||||
```text
|
||||
mesh_content = source_alpha × ben2_alpha × selection_mask
|
||||
```
|
||||
|
||||
由于 Refine 结果保持原 bounds,两个数组尺寸天然一致,不再需要局部 bounds 偏移或 Mask 重采样。组合结果只传给 BaseShape 构建、内容空值检查和网格标定;颜色图片及 Normal、Depth 贴图保持未乘 Selection Mask。
|
||||
|
||||
未启用 Refine、但因 Normal 或 Depth 提交 Job 的 Cutout 继续使用完整 bounds 输入,并在 Blender 中执行 `source_alpha × selection_mask`。两条 Cutout 响应路径因此都由同一个“贴图 Alpha × Selection Mask”入口构建网格。
|
||||
|
||||
### 5. Crop 在 Blender 中应用 Selection Mask
|
||||
|
||||
Crop Refine 同样接收完整 BEN2 RGBA,并沿用提交前 bounds。由于 Crop 的最终产物是图片而不是网格,Blender 必须把 BEN2 输出 Alpha 与原始 Selection Mask 相乘后创建结果图片:
|
||||
|
||||
```text
|
||||
crop_alpha = source_alpha × ben2_alpha × selection_mask
|
||||
```
|
||||
|
||||
Selection 栅格化仍只执行一次。Crop 提交阶段把局部 Mask 值写入与 Bounds 输入同一临时容器的本地 sidecar;该路径只由 Blender 响应读取,不进入 Job request。响应完成 Alpha 组合与结果 Image 创建后统一清理临时容器。相比把 Mask 发送到 Server或再次栅格化 SelectionPath,这保持了 Server 边界和一次栅格化约束。
|
||||
|
||||
Crop 结果画布和 placement bounds 保持提交前 Selection bounds,不再按组合后的可见 Alpha紧裁切。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [直接连接 Color 会让亮色 IOR Level 高于原默认 `0.5`] → 这是明确的颜色驱动语义;节点测试固定直连关系,不额外缩放。
|
||||
- [Geometry Nodes socket 改名会断开依赖旧接口名的内部设置] → 同步修改所有构建、验证、Modifier 设置和已存节点资产,不保留双 socket。
|
||||
- [Refine 图片不再紧裁切,透明边缘画布可能更大] → bounds 本来已限定为用户 Selection 的矩形范围,接受稳定对齐换取的少量图片尺寸成本。
|
||||
- [Crop Mask sidecar 增加一份临时文件] → 与 Bounds 输入共用生命周期,只保存单通道局部值,并由统一 cleanup 删除。
|
||||
- [Cutout 贴图含有 Selection 外的 BEN2 内容] → 最终网格由组合 Mask 严格限制,网格外纹理不会形成可见表面;AI 保留完整上下文是预期行为。
|
||||
- [Depth 的“统一内容范围”被误解为覆盖 EXR Alpha] → 测试和文档明确区分业务内容 Alpha 与模型 Validity,保持连续 Field 协议不变。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. 先修改 Refine Server 输出协议和测试,删除紧裁切与局部 bounds 产物。
|
||||
2. 调整 Crop 与 Cutout Blender 响应,以提交前 bounds 对齐 Mask 并构建最终图片或网格。
|
||||
3. 增加材质 Color-to-IOR-Level 连接及测试。
|
||||
4. 一次性重命名所有 Geometry Nodes socket、调用方和验证断言,重新构建并验证节点资产。
|
||||
5. 更新内部文档并运行相关测试和完整测试。
|
||||
|
||||
回退时整体恢复旧 Refine bounds 协议、旧 socket 名称和旧节点资产,不保留新旧并行入口。
|
||||
|
||||
## Open Questions
|
||||
|
||||
无。
|
||||
33
openspec/changes/refine-image-output-semantics/proposal.md
Normal file
33
openspec/changes/refine-image-output-semantics/proposal.md
Normal file
@@ -0,0 +1,33 @@
|
||||
## Why
|
||||
|
||||
Cutout 与 Plane 的材质高亮、Geometry Nodes 深度控制命名,以及 Refine Selection 的最终范围目前缺少一致语义。暗色区域仍会产生明显高亮,`Base Plane Depth` 暴露了多余的 Plane 概念,而 Refine 结果还会忽略用户圈选范围并改变提交前的 bounds。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 将 `O Image Layer` 的 Color 输出连接到生成材质 Principled BSDF 的 IOR Level,使原图暗色区域同步降低高亮。
|
||||
- 将所有 Geometry Nodes 接口中的 `Base Plane Depth` 统一改名为 `Reference Depth`,并同步 Modifier 初始化、节点资产验证和内部文档。
|
||||
- Refine Selection 保留送入 BEN2 前的矩形尺寸与 bounds;Server 不接收 Selection Mask,也不按 BEN2 Alpha 紧裁切结果。
|
||||
- Cutout 的 Color、Normal 与 Depth 统一基于 `Source Alpha × BEN2 Alpha` 的图片内容生成;Selection Mask 只在 Blender 中与该 Alpha 相乘,用于构建最终网格。
|
||||
- Crop Refine 同样保留提交前 bounds,并在 Blender 中使用 `Source Alpha × BEN2 Alpha × Selection Mask` 生成最终图片结果。
|
||||
- **BREAKING(内部 Job 产物协议)**:`refine-image-selection` 与 `generate-cutout-artifacts` 不再返回 Refine 后的局部 bounds 文件;调用方沿用提交前保存的 Selection bounds。
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `image-result-materials`: 规定 Plane、Depth Plane 与 Cutout 生成材质的颜色驱动 IOR Level 行为。
|
||||
- `geometry-depth-controls`: 规定 Depth Plane 与 Depth Cutout Geometry Nodes 的参考深度公开接口。
|
||||
- `selection-content-processing`: 规定 Refine Selection 的稳定 bounds、AI 贴图内容范围,以及 Crop 图片和 Cutout 网格对 Selection Mask 的最终约束。
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
无。当前 `openspec/specs` 尚无已归档 capability;本 change 将相关行为建立为当前规格。
|
||||
|
||||
## Impact
|
||||
|
||||
- Blender 材质构建:`src/anyimage/common/material.py` 及材质测试。
|
||||
- Geometry Nodes:Depth Plane、Cutout 构建与验证脚本,Modifier 输入设置,`O_AnyImage.blend` 节点资产及节点资产文档。
|
||||
- Refine 数据流:Crop Tool、Cutout Tool、BEN2 Refine Job、Cutout artifact Job、结果加载与内容 Mask 组合。
|
||||
- Job 结果:删除 Refine 局部 bounds 产物及对应读取逻辑。
|
||||
- 测试与文档:更新与“BEN2 可保留圈外内容”和紧裁切 bounds 相反的既有断言。
|
||||
- 不新增运行时依赖,不改变 SelectionPath JSON、BEN2/MoGe-2 模型或进程边界。
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Geometry depth groups expose Reference Depth
|
||||
系统 SHALL 在 `O Image Depth Plane`、`O Image Cutout`、Depth Balloon 与 Depth Surface 的 Geometry Nodes 接口中使用 `Reference Depth` 表示深度纹理的参考距离,并且 MUST 不再公开 `Base Plane Depth`。
|
||||
|
||||
#### Scenario: Depth Plane initializes its reference
|
||||
- **WHEN** 系统根据 `depth.json` 创建 Depth Plane Modifier
|
||||
- **THEN** `reference_depth × uniform_scale` SHALL 写入 `Reference Depth`
|
||||
|
||||
#### Scenario: Depth Cutout initializes its reference
|
||||
- **WHEN** 系统根据最终网格内容标定 Depth Balloon 或 Depth Surface
|
||||
- **THEN** 标定结果 SHALL 写入主 Cutout 与对应子组的 `Reference Depth`
|
||||
|
||||
#### Scenario: Node asset exposes the current interface only
|
||||
- **WHEN** 系统构建并验证 `O_AnyImage.blend`
|
||||
- **THEN** 所有相关节点组、面板、依赖连线和 Modifier 输入 SHALL 只包含 `Reference Depth`,不得保留旧名称或兼容 socket
|
||||
|
||||
### Requirement: Reference Depth keeps existing geometry meaning
|
||||
`Reference Depth` SHALL 继续作为采样 Depth 的运算基准,不改变 Depth Plane、Depth Balloon 或 Depth Surface 的几何方向、尺度、厚度和位移公式。
|
||||
|
||||
#### Scenario: Reference control changes relative depth
|
||||
- **WHEN** 用户修改 `Reference Depth`
|
||||
- **THEN** Geometry Nodes SHALL 使用 `Reference Depth - sampled depth` 的既有相对深度关系更新几何
|
||||
@@ -0,0 +1,16 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Image color drives generated material IOR Level
|
||||
系统 SHALL 将 Plane、Depth Plane 与 Cutout 的非 Shadeless 生成材质中 `O Image Layer` 的 Color 输出直接连接到 Principled BSDF 的 IOR Level,同时保留既有 IOR、Base Color、Roughness、Alpha、Normal 与 Displacement 连接。
|
||||
|
||||
#### Scenario: Dark image content suppresses highlights
|
||||
- **WHEN** 生成材质采样到黑色或暗色图片内容
|
||||
- **THEN** Principled BSDF 的 IOR Level SHALL 接收对应的低颜色值,从而降低该区域的高亮
|
||||
|
||||
#### Scenario: Bright image content preserves direct color response
|
||||
- **WHEN** 生成材质采样到白色或亮色图片内容
|
||||
- **THEN** Principled BSDF 的 IOR Level SHALL 接收 `O Image Layer` Color 的直接转换值,不经过额外缩放或固定上限
|
||||
|
||||
#### Scenario: Generated material keeps its IOR value
|
||||
- **WHEN** Cutout 或 Depth Plane 以 IOR `1.2` 创建材质
|
||||
- **THEN** 系统 SHALL 保留 IOR `1.2`,并由 Color 独立驱动 IOR Level
|
||||
@@ -0,0 +1,68 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Refine preserves the submitted bounds canvas
|
||||
系统 SHALL 让 BEN2 Refine 输出保持输入 RGBA 的完整矩形尺寸,并沿用提交前的 Selection bounds;Server MUST 不按生成 Alpha 紧裁切输出,也 MUST 不接收 Selection Mask。
|
||||
|
||||
#### Scenario: BEN2 output occupies only part of the bounds
|
||||
- **WHEN** BEN2 只在输入矩形的一部分生成可见 Alpha
|
||||
- **THEN** Refine 输出 SHALL 保留完整输入尺寸和透明区域,Blender SHALL 沿用提交前保存的 bounds
|
||||
|
||||
#### Scenario: Refine Job reports its files
|
||||
- **WHEN** `refine-image-selection` 或 Cutout artifact Job 完成 Refine
|
||||
- **THEN** Job SHALL 返回完整尺寸的 Refine 图片,并且 MUST 不生成或返回 Refine 后的局部 bounds 文件
|
||||
|
||||
### Requirement: Refined content Alpha combines source and BEN2
|
||||
BEN2 Refine 内容 Alpha SHALL 等于 `Source Alpha × BEN2 Alpha`,并且 Selection Mask MUST 不参与 BEN2 推理或 Server 端 Refine 输出。
|
||||
|
||||
#### Scenario: Source contains transparent pixels
|
||||
- **WHEN** BEN2 在源 Alpha 为零或半透明的像素生成前景 Alpha
|
||||
- **THEN** Refine 图片 SHALL 通过 `preserve_input_alpha` 保持源 Alpha 与 BEN2 Alpha 的乘积
|
||||
|
||||
#### Scenario: Selection excludes visible content inside bounds
|
||||
- **WHEN** bounds 内存在位于 Selection Mask 外的可见内容
|
||||
- **THEN** BEN2 与 Server Refine 输出 SHALL 仍能使用并保留该内容,不得预先乘入 Selection Mask
|
||||
|
||||
### Requirement: Cutout textures share refined content scope
|
||||
启用 Refine Selection 的 Cutout SHALL 使用同一份 `Source Alpha × BEN2 Alpha` 内容生成 Color、Normal 与 Depth 贴图,Selection Mask MUST 不限制这些贴图的生成。
|
||||
|
||||
#### Scenario: Cutout generates color and normal
|
||||
- **WHEN** Cutout 同时启用 Refine Selection 与 Normal Map
|
||||
- **THEN** Color Alpha 与 Normal 可见区域 SHALL 来自完整 Refine 图片 Alpha,不得乘入 Selection Mask
|
||||
|
||||
#### Scenario: Cutout generates depth
|
||||
- **WHEN** Refined Cutout 生成 Vector 或 Z Depth
|
||||
- **THEN** MoGe-2 黑底输入和 Depth Metadata 参考内容范围 SHALL 来自完整 Refine 图片 Alpha,不得乘入 Selection Mask
|
||||
|
||||
#### Scenario: Depth texture preserves model validity
|
||||
- **WHEN** 系统写出 Vector 或 Z Depth EXR
|
||||
- **THEN** RGB SHALL 保留完整连续 Field,Alpha SHALL 保存 MoGe-2 Validity,不得用 Refine Alpha 或 Selection Mask 覆盖
|
||||
|
||||
### Requirement: Cutout mesh combines refined content with Selection
|
||||
Cutout SHALL 只在 Blender 网格构建阶段计算 `Source Alpha × BEN2 Alpha × Selection Mask`,并使用该结果约束 BaseShape、内容空值检查和网格标定。
|
||||
|
||||
#### Scenario: BEN2 keeps content outside the user Selection
|
||||
- **WHEN** BEN2 在 Selection bounds 内保留了 Selection Mask 外的前景
|
||||
- **THEN** Color、Normal 与 Depth SHALL 保留该内容,但最终 Cutout 网格 MUST 不包含 Selection Mask 外的区域
|
||||
|
||||
#### Scenario: Combined Cutout content is empty
|
||||
- **WHEN** Refine Alpha 与 Selection Mask 相乘后没有超过 Cutout Alpha 阈值的像素
|
||||
- **THEN** Cutout SHALL 报告空内容且不创建网格
|
||||
|
||||
#### Scenario: Cutout generates only Normal or Depth
|
||||
- **WHEN** Cutout 未启用 Refine、但因 Normal 或 Depth 进入 Job
|
||||
- **THEN** 贴图 SHALL 使用完整 bounds 源 Alpha,网格 SHALL 使用 `Source Alpha × Selection Mask`
|
||||
|
||||
### Requirement: Crop Refine applies Selection in Blender
|
||||
Crop Refine SHALL 在 Blender 中计算 `Source Alpha × BEN2 Alpha × Selection Mask` 作为最终图片 Alpha,同时保持提交前的矩形尺寸和 placement bounds。
|
||||
|
||||
#### Scenario: BEN2 keeps pixels outside a Crop Selection
|
||||
- **WHEN** BEN2 在 Crop Selection Mask 外生成可见 Alpha
|
||||
- **THEN** 最终 Crop 图片 SHALL 将这些像素 Alpha 置为零,但 MUST 不改变提交前 bounds
|
||||
|
||||
#### Scenario: Crop uses one rasterized Selection
|
||||
- **WHEN** Crop 提交 Refine Job 并异步接收结果
|
||||
- **THEN** 系统 SHALL 复用提交阶段生成的 Selection Mask,不得在响应阶段重新栅格化 SelectionPath,也不得把 Mask 发送给 Server
|
||||
|
||||
#### Scenario: Combined Crop content is empty
|
||||
- **WHEN** Refine Alpha 与 Selection Mask 相乘后没有可见像素
|
||||
- **THEN** Crop SHALL 报告空内容且不创建结果 Image Empty
|
||||
30
openspec/changes/refine-image-output-semantics/tasks.md
Normal file
30
openspec/changes/refine-image-output-semantics/tasks.md
Normal file
@@ -0,0 +1,30 @@
|
||||
## 1. Refine Server 输出协议
|
||||
|
||||
- [x] 1.1 修改通用 BEN2 Refine,使其保存与输入同尺寸的 `Source Alpha × BEN2 Alpha` RGBA,删除 Alpha 紧裁切与局部 bounds 计算;用像素和尺寸测试验证透明、半透明与前景局部输出。
|
||||
- [x] 1.2 修改 `refine-image-selection` 与 Cutout artifact Job,只返回完整 Refine 图片并删除 `selection_bounds` 文件和结果 key;更新 Job 协议测试并确认 request 不包含 Selection Mask。
|
||||
- [x] 1.3 验证 Cutout Refine 后的 Color、Normal 可见区域、MoGe-2 黑底输入和 Depth Metadata 都消费完整 Refine Alpha,同时 Depth EXR 继续保留连续 RGB Field 与模型 Validity Alpha。
|
||||
|
||||
## 2. Crop 与 Cutout 最终 Selection 约束
|
||||
|
||||
- [x] 2.1 为 Crop Refine 准备与 Bounds 输入同生命周期的本地 Selection Mask sidecar,确保 Job request 不发送该路径且 cleanup 能完整删除临时容器。
|
||||
- [x] 2.2 修改 Crop Refine 响应,沿用提交前 bounds,在 Blender 中计算 `Refine Alpha × Selection Mask` 并创建完整 bounds 结果;用圈外 BEN2 内容、空交集、Keep Original 和一次栅格化测试验证。
|
||||
- [x] 2.3 修改 Cutout Job 响应,让启用 Refine 与仅生成 Normal/Depth 两条路径都以“贴图 Alpha × 原 Selection Mask”构建网格,同时保持颜色及 AI 贴图未乘 Selection;用圈外内容、抗锯齿 Mask、空交集和标定测试验证。
|
||||
- [x] 2.4 删除 Blender 端 Refine 局部 bounds 偏移与结果读取逻辑,验证 Crop 和 Cutout 的图片尺寸、placement bounds、颜色资产复用和异常清理。
|
||||
|
||||
## 3. 生成材质 IOR Level
|
||||
|
||||
- [x] 3.1 将 `O Image Layer` Color 直接连接到 Plane、Depth Plane 与 Cutout 生成材质的 Principled BSDF IOR Level,保留既有 IOR 和其他材质连接。
|
||||
- [x] 3.2 增加材质节点测试,验证 Color-to-IOR-Level 直连、Cutout/Depth Plane IOR `1.2`、普通 Plane 覆盖以及 Shadeless 分支不回归。
|
||||
|
||||
## 4. Reference Depth 节点接口
|
||||
|
||||
- [x] 4.1 将 Depth Plane、Cutout、Depth Balloon 与 Depth Surface 构建脚本中的 `Base Plane Depth` 一次性改名为 `Reference Depth`,同步 Group Input 可见项、子组连线和面板顺序。
|
||||
- [x] 4.2 更新 Depth Plane 与 Cutout 对 Modifier 输入的初始化、查找和测试,确保元数据与标定值只写入 `Reference Depth` 且几何公式不变。
|
||||
- [x] 4.3 更新节点资产验证脚本和结构断言,确认所有相关接口、依赖集合与功能验证不再包含旧名称。
|
||||
- [x] 4.4 运行 `uv run build_node` 重建并验证 `assets/O_AnyImage.blend`,检查 socket 顺序、节点布局、几何求值和未使用输出隐藏。
|
||||
|
||||
## 5. 文档与验证
|
||||
|
||||
- [x] 5.1 更新 Crop Tool、Cutout Tool、Convert to Plane、Runtime、Outputs 与节点资产内部文档,说明稳定 Refine bounds、贴图与网格 Mask 边界、IOR Level 连接和 `Reference Depth`。
|
||||
- [x] 5.2 搜索并删除当前实现和文档中的 `Base Plane Depth`、Refine 局部 bounds 协议及“BEN2 圈外内容直接进入最终网格”的残留描述。
|
||||
- [x] 5.3 运行相关 Server、Blender、Cutout、材质和节点资产测试,再运行 `uv run pytest` 完整测试;仅修复本 change 引入的回归,不构建其他文档或发布产物。
|
||||
Reference in New Issue
Block a user