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:
2026-09-03 01:02:10 +08:00
parent a6041cbe03
commit bdbde1a10d
47 changed files with 1120 additions and 257 deletions

View File

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

View File

@@ -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` 中已经创建的数据块。若回退,代码可整体恢复;已有结果数据不需要转换。

View File

@@ -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 协议。

View File

@@ -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** 系统等比增大网格采样间距,使实际采样长边不超过配置值

View File

@@ -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 数据继续按对应材质贴图规则命名

View File

@@ -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`,确认全部测试通过且没有恢复旧命名入口、兼容层或图片尺寸限制胶水代码