feat(node_assets): add image plane geometry nodes and validation scripts
- Implemented `image_plane.py` to build the Image Plane Geometry Nodes group with customizable parameters for geometry, subdivide, and thickness. - Created `image_plane_validation.py` to validate the functionality of the image plane, ensuring correct geometry generation and material assignment. - Developed `material_layers.py` to construct shader node groups for image materials, including depth and normal handling. - Authored `plane_layouts.py` to define node arrangements for image geometry assets. - Introduced `shadeless.py` to create a shadeless shader node group that displays color without scene lighting while preserving shadows and reflections.
This commit is contained in:
@@ -38,4 +38,4 @@ flowchart TD
|
||||
|
||||
落点仍在 `actions.py` 中完成:3D View 创建 Reference 或 Plane,Node Editor 创建与当前节点树匹配的 Image 节点,绘制模式把 Image 配置为当前画笔的 Texture。鼠标位置和视图位置也在这里换算。
|
||||
|
||||
Plane 路径调用 `convert_to_plane/mesh_plane.py` 创建中心原点基础网格,并从节点资产加载 `O Mesh Plane`。`actions.py` 创建图像材质并放入 Mesh 第 `0` 个材质槽,节点组只生成几何。
|
||||
Plane 路径调用 `convert_to_plane/image_plane.py` 创建中心原点基础网格,并从节点资产加载 `O Image Plane`。`actions.py` 创建图像材质并放入 Mesh 第 `0` 个材质槽,节点组只生成几何。
|
||||
|
||||
@@ -1,56 +1,23 @@
|
||||
# Plane 转换
|
||||
|
||||
Plane 转换从当前 Image Empty 创建普通 Plane 或 Depth Plane:普通 Plane 同步完成,Depth Plane 先取得 MoGe-2 Z Depth 与 Tangent Normal 文件,再回到 Blender 创建对象。
|
||||
|
||||
## 执行过程
|
||||
`operators/convert_to_plane/conversion.py` 提供 `ConvertToPlane`、`ConvertToDepthPlane` 与 `GenerateDepthPlane`。对象和材质导入位于 `object.py`,基础矩形网格位于 `image_plane.py`。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Empty["Image Empty"] --> Kind{"转换类型"}
|
||||
Kind -- "Plane" --> Color["复制 Color Image"]
|
||||
Color --> Plane["创建 Plane Object"]
|
||||
Kind -- "Depth Plane" --> Job["generate-depth-plane-geometry"]
|
||||
Job --> Result["Color Image + z-depth.exr + depth.json + tangent-normal.png"]
|
||||
Result --> Import["加载并 Pack Depth / Normal Image"]
|
||||
Import --> Depth["创建 Depth Plane Object"]
|
||||
A[Image Empty] --> B{转换类型}
|
||||
B -- Plane --> C[复制 Color 与动画设置]
|
||||
C --> D[中心原点四边形与 O Image Plane]
|
||||
B -- Depth Plane --> E[检查静态图与 AI 就绪状态]
|
||||
E --> F[generate-depth-plane-geometry]
|
||||
F --> G[Color、depth.exr、depth.json、Tangent Normal]
|
||||
G --> H[加载并 Pack 数据]
|
||||
H --> I[O Image Depth Plane 与材质]
|
||||
D --> J[替换源 Empty 并保留世界位置]
|
||||
I --> J
|
||||
```
|
||||
|
||||
两条路径共用 Image Empty 的显示范围、颜色材质和中心原点 Plane。Depth Plane 通过一次 MoGe-2 Server Job 同时生成 Z Depth 与 Tangent Normal;节点根据相机元数据生成单面地形体块,Normal Map 则进入材质着色。
|
||||
普通 Plane 支持图片、视频和序列,材质保留 Image User 帧设置。基础四边形以局部中心为原点,对象矩阵补偿原 Image Empty 显示范围;节点组生成细分与厚度,材质位于 Mesh 第 0 个槽。
|
||||
|
||||
## 节点说明
|
||||
Depth Plane 从 Preferences 取得 MoGe-2 模型、分辨率级别与 AI 输入上限,一次推理生成相机 XYZ 深度和切线法线。`create_depth_plane_from_result()` 创建颜色材质与深度修改器,并在失败时清理未使用的数据块。
|
||||
|
||||
### 选择 Plane 或 Depth Plane
|
||||
|
||||
`operators/convert_to_plane/__init__.py` 导出注册类型,实际入口在 `conversion.py`。`ConvertToPlane` 进入本地路径,`ConvertToDepthPlane` 检查静态图片和 AI 环境后启动 `GenerateDepthPlane`。
|
||||
|
||||
### 创建普通 Plane
|
||||
|
||||
`conversion.py` 先调用 `object.create_color_image()` 复制当前 Image,再由 `object.create_plane_object()` 创建对象。`mesh_plane.py` 把基础四边形顶点放在局部原点两侧,并把 Image Empty 显示范围中心补入对象矩阵,因此世界空间位置不变而对象原点位于面中心。随后从节点资产加载 `O Mesh Plane` 修改器。
|
||||
|
||||
材质存入 Mesh 第 `0` 个材质槽。`O Mesh Plane` 将带匿名标记的输入几何与生成几何临时合并,统一设置材质索引 `0` 后再删除输入几何,从而让纯生成面保留原 Mesh 的材质集合。Movie 或 Sequence 的 Image User 设置会保留。转换完成后,新 Plane 替换源 Empty,并沿用原对象名称。
|
||||
|
||||
### 生成 Depth 与 Normal
|
||||
|
||||
Depth 路径中的 `GenerateDepthPlane` 位于 `conversion.py`。它从 Preferences 读取 **Depth & Normal Model**、**MoGe Resolution Level** 和 **Maximum AI Input Size**,提交 `generate-depth-plane-geometry` Job。
|
||||
|
||||
`server/jobs/depth_plane.py` 编排完整图片任务,`server/geometry/prediction_artifacts.py` 把图片合成到黑底后调用一次 MoGe-2,并从同一个 `GeometryFrame` 写出 `z-depth.exr`、`depth.json` 与 `tangent-normal.png`。该 Artifact 生成层也由 Cutout Job 复用;两种顶层 Job 继续各自负责完整图片与 Selection 的输入、结果协议。
|
||||
|
||||
EXR 的 RGB 重复保存完整相机 Z,Alpha 保存模型 Validity;JSON 保存图片尺寸、相机内参与有效区域的参考深度。Tangent Normal 使用 PNG 保存,只参与材质着色。AI 输入上限只影响模型分析图,颜色副本仍保留原始分辨率。
|
||||
|
||||
### 创建 Depth Plane
|
||||
|
||||
Job 返回后,`object.create_depth_plane_from_result()` 加载并 Pack `z-depth.exr` 与 `tangent-normal.png`。Depth 数据块仍由 `depth_data_name()` 命名;Normal 图片设为 Non-Color,并以 Tangent Space 接入 `O Image Layer`。结果材质与 Cutout 一样使用 Principled IOR `1.2`,并由 `O Image Layer` Color 直接驱动 IOR Level。`depth.json` 的参考深度乘以统一尺度后初始化 **Reference Depth**,使它和采样深度处于同一局部距离单位。
|
||||
|
||||
`O Image Depth Plane` 把 `O Mesh Plane` 的固定底面放在原图片平面,`Thickness` 只沿正法线移动可变面与中间环,再按各层位置应用深度位移:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Depth["Z Depth × Uniform Scale"] --> Offset["(Reference Depth - Depth) × Depth Scale"]
|
||||
Offset --> Clamp["负向位移限制在固定底面之前"]
|
||||
Block["O Mesh Plane 厚度体块"] --> Weight["固定底面 0 → 中间环 → 可变面 1"]
|
||||
Clamp --> Apply["位移 × 层权重"]
|
||||
Weight --> Apply
|
||||
Apply --> Terrain["单面地形体块"]
|
||||
```
|
||||
|
||||
固定底面位于原图片平面,未置换可变面位于正法线方向的 **Thickness** 距离处。**Depth Scale** 为 `0` 时成品厚度等于 **Thickness**;正向位移可以超出基础厚度,负向位移不会穿过固定底面。`conversion.py` 最后用一个 Undo 步骤替换源 Empty。
|
||||
`O Image Depth Plane` 提供默认 Relief 和 Camera:Relief 读取深度 Z 形成固定底面的地形;Camera 使用相机 XYZ 投影并沿平滑法线形成壳。两个模式各自保存 Thickness。完整接口见[节点资产](../node-assets.md),文件协议见[Job 产物](../outputs.md)。
|
||||
|
||||
@@ -1,108 +1,39 @@
|
||||
# Cutout Tool
|
||||
# Cutout
|
||||
|
||||
Cutout 从图片上的一个 Selection 开始,可按需生成 Depth 或 Normal Map,最后在 Blender 中创建带材质与 Modifier 的 Mesh Object。
|
||||
|
||||
## 执行过程
|
||||
Cutout 在静态 Image Empty 上圈定 Selection,按轮廓创建 Flat、Solid、Depth Solid 或 Depth Balloon。入口位于 `operators/cutout_tool/main.py`,菜单与采样级别映射位于 `interaction.py`,标识和资产对应关系位于 `shape.py`。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Select["圈定 SelectionPath"]
|
||||
Menu["打开 Shape Pie Menu"]
|
||||
Shape["选择 Shape"]
|
||||
Raster["SelectionPath → 几何 SelectionMask"]
|
||||
Route{"需要后台生成数据?"}
|
||||
Local["读取局部 RGBA<br/>源 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
|
||||
A[Lasso 拖动并提交 SelectionPath] --> B[选择 Shape]
|
||||
B --> C[栅格化 SelectionMask 并读取源 RGBA]
|
||||
C --> D{需要 Depth 或 Normal?}
|
||||
D -- 是 --> E[准备 bounds 矩形 RGBA]
|
||||
E --> F[generate-cutout-artifacts]
|
||||
F --> G[加载 Depth、Metadata 与 Normal]
|
||||
G --> H[源 Alpha 与 SelectionMask 组合]
|
||||
D -- 否 --> H
|
||||
H --> I[轮廓、采样、约束三角化与 o_balloon]
|
||||
I --> J[BaseShape]
|
||||
J --> K[创建材质、对象与对应节点修改器]
|
||||
```
|
||||
|
||||
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 如何形成结果 | 必需后台产物 |
|
||||
| 预设 | 节点组 | AI 产物 |
|
||||
| --- | --- | --- |
|
||||
| Surface | 保持基础平面轮廓,可增加真实厚度 | 无 |
|
||||
| Balloon | 使用 `o_balloon` 从边缘向内部鼓起 | 无 |
|
||||
| Depth Balloon | 在 Balloon 上叠加相对基准面的深度 | `z-depth.exr`、`depth.json` |
|
||||
| Depth Surface | 使用相机点 XYZ 重建透视表面 | `vector-depth.exr`、`depth.json` |
|
||||
| Flat | `O Image Cutout`,Thickness 0 | 可选 Tangent Normal |
|
||||
| Solid | `O Image Cutout`,Thickness 1 | 可选 Tangent Normal |
|
||||
| Depth Solid | `O Image Depth Cutout` | Depth 与 Object Normal |
|
||||
| Depth Balloon | `O Image Depth Balloon` | Depth 与 Tangent Normal |
|
||||
|
||||
Depth Shape 的 Reference Depth,以及 Depth Balloon 的 Direction,都由 `object.py` 调用 `geometry.py` 的标定函数,根据最终网格内容 Mask 计算。透明像素达到 `5%` 时使用有效内容区域自适应标定,否则使用稳定的固定基准。
|
||||
## 轮廓与网格
|
||||
|
||||
Normal Map 关闭时材质使用几何法线;开启时,`object.py` 把生成的图片和已经确定的 Normal Space 交给材质节点。最后 `main.py` 提交 Undo,整条 Cutout 流程结束。
|
||||
`geometry.py` 从内容 Alpha 提取连通轮廓和孔洞,按 Mesh Detail 采样并生成 `BaseShape`。Low、Medium、High、Ultra 分别以 32、16、8、4 个源像素作为基础间距,并受 Maximum Cutout Mesh Resolution 限制。
|
||||
|
||||
### Inward Axis
|
||||
`fine_mesh.py` 负责 Fine Outline 的细节保留与约束网格,`polygon.py` 提供轮廓运算。Balloon Profile 按 Preferences 选择 Poisson 或 Teddy,生成所有预设共用的 POINT 属性 `o_balloon`。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Base["BaseShape 与四种 Shape<br/>原生 XY / Z"] --> Switch{"Inward Axis"}
|
||||
Switch -- "-Z(默认)" --> Native["节点几何直接输出<br/>对象沿用源 Empty local transform"]
|
||||
Switch -- "+X" --> Orient["O Image Cutout 执行 Z → X<br/>对象矩阵执行 X → Z"]
|
||||
Native --> World["相同世界空间结果"]
|
||||
Orient --> World
|
||||
Normal["Depth Surface<br/>Object Space Normal"] --> Mode{"Inward Axis"}
|
||||
Mode -- "-Z" --> Keep["保持原生 Z 轴通道"]
|
||||
Mode -- "+X" --> Swizzle["按 Z → X 转换一次"]
|
||||
```
|
||||
## AI 与对象创建
|
||||
|
||||
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 不变。
|
||||
Server 接收 Selection bounds 内保留源 Alpha 的 RGBA,一次 MoGe-2 推理按 `generate_depth` 与 `normal_mode` 返回产物。深度边缘延展使用所选 Alpha Threshold,Normal 使用原预测;AI 输入上限只影响分析分辨率。
|
||||
|
||||
Balloon 与 Depth Balloon 的材质会创建 Geometry Attribute 节点,把 `o_balloon` 的 Float 输出直接连接到 `O Image Layer` 已有的 **Normal Scale**;Surface 与 Depth Surface 不建立这条连接。
|
||||
`object.py` 的 `create_shape_object()` 消费 BaseShape、Color、Depth 和 Normal,加载对应节点组。基础网格位于局部 XY,向图片内部为 -Z;选择 +X 时节点与对象矩阵配对变换,保持世界几何一致。Object Normal 同步按方向转换。
|
||||
|
||||
材质使用 IOR 1.2,颜色进入 Image Layer,`o_normal_scale` 控制法线影响。Depth Solid 使用正面 FACE 标记,其他适用预设使用轮廓权重。节点计算见[节点资产](../node-assets.md)。
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Debug Operator
|
||||
|
||||
Debug Operator 从面板中的模型参数发起一次原始推理,把结果文件写入 Job 目录并打开目录供开发者检查。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Panel["Debug 面板"] --> Operator["选择模型 Debug Operator"]
|
||||
Operator --> Request["读取 Scene 参数"]
|
||||
Request --> Job["提交对应 Debug Job"]
|
||||
Job --> Output["写出原始预测产物"]
|
||||
Output --> Directory["打开 Job 目录"]
|
||||
```
|
||||
|
||||
DA3、MoGe-2、BEN2 和 Upscale 都走这条调试路径,用于验证模型、设备、请求参数和输出格式。
|
||||
|
||||
## 节点说明
|
||||
|
||||
### 从 Debug 面板进入
|
||||
|
||||
入口显示在 `src/anyimage/panel.py`,四个 Operator 定义在 `operators/debug.py`:`DebugDa3`、`DebugMoge2`、`DebugBen2` 和 `DebugUpscale`。它们读取 `Scene.anyimage_settings` 中的输入路径、设备、模型和分辨率参数,并检查 Environment 与模型是否可用。
|
||||
|
||||
### 提交原始推理
|
||||
|
||||
`debug.py` 分别提交 `debug-da3`、`debug-moge2`、`debug-ben2` 和 `debug-upscale` Job。Server 侧由 `server/jobs/debug_predictions.py` 接收请求,再调用相应模型适配器。
|
||||
|
||||
DA3 和 MoGe-2 的可视化与数组输出由 `server/outputs/debug_da3.py`、`server/outputs/debug_moge2.py` 写出;BEN2 与 Upscale 保存模型返回的图片结果。
|
||||
|
||||
### 查看结果
|
||||
|
||||
任务完成后,`debug.py` 的 `_open_job_directory()` 调用 Blender 的路径打开操作,直接显示该 Job 的目录。
|
||||
21
docs/internals/operators/frame.md
Normal file
21
docs/internals/operators/frame.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Frame 多图合成
|
||||
|
||||
`operators/image_edit_tool/frame.py` 定义 `FrameImages`、`FrameTool` 和工具激活入口。Frame 将选中的静态 Image Empty 按当前视图合成为一张图片。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[拖出屏幕矩形] --> B[冻结视图矩阵并收集选中静态图片]
|
||||
B --> C[检查矩形与 active 图片的有效投射交集]
|
||||
C --> D[按 Region 与 Maximum Frame Resolution 计算输出尺寸]
|
||||
D --> E[观察射线投影到各图片平面]
|
||||
E --> F[按样本深度从远到近合成,等深度 active 优先]
|
||||
F --> G[边界覆盖采样与独立 Alpha]
|
||||
G --> H[创建 Packed Image 并复用 active Object]
|
||||
H --> I[删除其他参与图片对象并提交 Undo]
|
||||
```
|
||||
|
||||
正交视图接受有限深度,透视视图采样观察方向前方的交点。交叠检查采用连续投影范围;输出尺寸受 Region、最长边上限和总像素上限约束。
|
||||
|
||||
RGB 与 Alpha 分别累计,保留被 Mask 隐藏的有效颜色。边界使用覆盖采样,内部复用直接采样,避免黑边和不必要的模糊。结果平面朝向观察者,位置由 active 图片的有效深度确定。
|
||||
|
||||
Frame 保留 active 对象身份、名称和 Collection;图像替换复用 [Common](../common.md) 的共享数据与失败恢复逻辑。
|
||||
@@ -1,57 +0,0 @@
|
||||
# Image Edit Tool
|
||||
|
||||
工具栏包含 **Frame**、**Mask**、**Rectify** 三合一 Image Edit 组,Cutout 作为紧接其后的独立工具。
|
||||
|
||||
## 工具设置
|
||||
|
||||
Frame 和 Rectify 没有业务选项。Frame 让 active Image Empty 承载合成结果,并删除其他参与合并的选中 Image Empty;Rectify 直接替换 active Image Empty 的图片。
|
||||
|
||||
Mask 保存 **Gesture**、**Mode** 和 **Radius**。Gesture 包含 Lasso、Brush 与 Polyline;Mode 包含 Set、Add 与 Subtract,默认使用 Subtract;Radius 默认 25 px,仅在 Brush 时显示和生效。
|
||||
|
||||
## 执行过程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Group["Frame / Mask / Rectify"]
|
||||
Group --> Kind{"子工具"}
|
||||
Kind -- "Frame" --> Sources["选中 still Image Empty<br/>active 为对象和深度基准"]
|
||||
Sources --> Project["Frame 像素生成观察射线<br/>与图片平面求交"]
|
||||
Project --> Composite["逐像素深度与 Alpha 合成"]
|
||||
Composite --> Canvas["完整 Frame Canvas<br/>范围外透明"]
|
||||
Canvas --> Facing["active 朝向当前视角<br/>删除其他参与对象"]
|
||||
Kind -- "Mask" --> Gesture{"Lasso / Brush / Polyline"}
|
||||
Gesture --> Mask["栅格化 SelectionMask"]
|
||||
Mask --> Mode{"Set / Add / Subtract"}
|
||||
Mode --> Alpha["只合成当前 Alpha<br/>保持完整 Canvas"]
|
||||
Kind -- "Rectify" --> Quad["四点 Quad 与 Homography"]
|
||||
Alpha --> Replace["替换 active Empty 的 Image"]
|
||||
Quad --> Replace
|
||||
```
|
||||
|
||||
## Frame
|
||||
|
||||
`frame.py` 读取所有选中的 still Image Empty,并要求 active object 是 Selection 内的 still Image Empty。仅有 active 状态但未选中的图片不构成输入;active 是非图片对象时,即使 Selection 中存在图片也以 warning 取消。Frame 可以从 Viewport 任意位置开始,但结果矩形必须与 active 图片投射相交。选中的非 Image Empty 被忽略;任一选中 Image Empty 是视频或序列时整体取消。
|
||||
|
||||
每个参与图片使用冻结的 Viewport 投影矩阵与对象世界矩阵建立屏幕到图片平面的 Projective Transform。Frame 输出像素中心沿当前观察射线映射到图片局部平面,再换算为源像素坐标;有限且位于 Image Empty 显示边界内的交点参与采样。正交视图接受任意有限视图深度;透视视图只接受观察方向前方的交点。图片部分跨越透视观察原点时,前方区域继续采样,后方、平行射线和图片范围外透明。
|
||||
|
||||
输出像素按观察射线上的几何深度从远到近排序,再以预乘 Alpha 合成;同深度时 active 位于最上层。Frame 与 active 的交叠通过连续有效投射区域和 Frame 矩形的正面积交集判断,不依赖源 Alpha 或离散输出像素中心。输出分辨率使用 Frame 在当前 Region 中的屏幕像素宽高,不超过 Region 尺寸,并由 Preferences 的 **Maximum Frame Resolution** 等比限制最长边;总像素不超过 16,777,216。透明边缘保留在完整 Canvas 中;紧邻内容的一圈透明像素保留相邻 RGB,避免线性插值产生黑边。
|
||||
|
||||
Frame 与 Cutout Lasso 只在 `CLICK_DRAG` 后进入手势。Mask 与 Rectify 使用 `PRESS` 启动。三个绘制型工具都由 Blender 原生点选决定命中对象:点到另一对象时只切换 active,不开始编辑;没有命中对象时保留当前 active Image Empty,因此透明区域和 Viewport 空白都可作为起点。Mask 使用 `PAINT_CROSS` cursor;Brush 单击生成一个圆形印记。Polyline 通过逐点单击建立路径,至少三个点后在起点周围显示随鼠标接近而放大的闭合圈,并以点击 15 px UI Scale 命中范围、双击或 Enter 完成;Backspace 删除最后一点。手势与图片范围、可见内容的有效性在提交阶段判断。Frame 的普通空白点击继续清空选择,Shift 点击由显式 Viewport 对象选择 keymap 处理。
|
||||
|
||||
结果平面本地 XY 平行视平面且正面朝向观察者。正交视图与 active 原点位于透视观察方向前方时,结果沿用 active 对象原点的视图深度;透视 active 原点位于视后方但图片仍有有效前方交叠时,结果使用有效交叠区域屏幕中心与 active 图片平面的交点深度。active Blender Object 保留名称、Collection 和身份并承载 packed 合成 Image,其他参与 Image Empty 随后删除;整个操作属于一个 Undo 步骤。
|
||||
|
||||
## Mask
|
||||
|
||||
`mask.py` 定义一个 WorkspaceTool 和一个 `EditImageAlpha` Operator。Operator 启动时冻结 Scene 中保存的 Gesture、Mode 和 Radius;Lasso 把自由手绘闭合路径转换为图片像素,Polyline 把逐点闭合路径送入同一个 SelectionMask 与 Alpha 合成入口。
|
||||
|
||||
Brush 拖动阶段只维护经过距离与近共线简化的屏幕轨迹,并为每个采样点生成圆、为每对相邻点生成等宽连接条;不读取源 RGBA,不创建临时 Image,也不投影、栅格化或更新 Pixel Buffer。Overlay 在局部 screen-space scanline bands 中合并这些 Primitive,生成无内部接缝、尖角或三角孔的一次填充与闭合外轮廓;Subtract 为红色,Set/Add 为灰色。释放后把同一组 Primitive 投影到图片,以逐像素最大值合入单个覆盖画布,再通过公共 Alpha 合成与 Image 替换入口提交 Packed 结果。取消或切换工具只结束 Overlay,源 Image 与共享引用始终不变。
|
||||
|
||||
设当前 Alpha 为 `A`、手势权重为 `M`:Set 计算 `A × M`,Add 计算 `max(A, M)`,Subtract 计算 `A × (1 - M)`。RGB 原样保留。结果始终使用源图片宽高和原有画框,因此 Image Empty 的 identity、matrix、display size 和 offset 保持不变;全透明结果同样有效。
|
||||
|
||||
## Rectify
|
||||
|
||||
`rectify.py` 接受任意顺序的四个角点并规范为凸 Quad,创建 512 × 512 预览,随后按目标宽高比执行分块 Homography。输出最长边不超过 8192 px,总像素不超过 16,777,216;插值使用预乘 Alpha,结果按可见 Alpha trim 后映射回源图片平面。
|
||||
|
||||
Mask 与 Rectify 最终都以 packed Image 调用 `common/image.py` 的 `replace_empty_image()`。Mask 不提供 placement bounds;Rectify 使用校正区域的 placement bounds 调整图片画框。Frame 复用同模块的 Image 创建、Homography、预乘采样和单对象替换函数,并在自身业务边界完成多图合成与对象删除。
|
||||
|
||||
三个工具的结果都以源 Image 的完整名称请求命名,不移除 `.png` 等名称内容,也不添加材质贴图后缀。源数据仍被其他对象引用时,Blender 为结果追加数字后缀;源数据可清理时,替换入口让结果恢复准确原名。
|
||||
@@ -1,17 +1,15 @@
|
||||
# Operator 实现索引
|
||||
|
||||
`src/anyimage/operators/` 负责 Blender 侧交互、Job 请求组装和结果导入。
|
||||
| 源码(相对 `src/anyimage/`) | 功能 |
|
||||
| --- | --- |
|
||||
| `operators/ai_setup.py` | [AI 环境管理](ai-setup.md) |
|
||||
| `operators/clipboard_image/` | [剪贴板图片](clipboard-image.md) |
|
||||
| `operators/convert_to_plane/` | [Plane 与 Depth Plane](convert-to-plane.md) |
|
||||
| `operators/cutout_tool/` | [Cutout](cutout-tool.md) |
|
||||
| `operators/image_edit_tool/frame.py` | [Frame 多图合成](frame.md) |
|
||||
| `operators/image_edit_tool/mask.py` | [Mask Alpha 编辑](mask.md) |
|
||||
| `operators/image_edit_tool/rectify.py` | [Rectify 透视校正](rectify.md) |
|
||||
| `operators/remove_background.py` | [Remove Background](remove-background.md) |
|
||||
| `operators/upscale.py` | [Upscale](upscale.md) |
|
||||
|
||||
| Operator 路径 | 实现页 | 主要职责 |
|
||||
| --- | --- | --- |
|
||||
| `operators/__init__.py` | 本页 | 汇总 Blender 类型注册顺序 |
|
||||
| `operators/ai_setup.py` | [AI 环境管理](ai-setup.md) | 环境安装、模型下载与缓存释放 |
|
||||
| `operators/clipboard_image/` | [Clipboard Image](clipboard-image.md) | 系统剪贴板读取、粘贴与快捷键 |
|
||||
| `operators/convert_to_plane/` | [Convert to Plane](convert-to-plane.md) | Mesh Plane 与 Depth Plane 创建 |
|
||||
| `operators/cutout_tool/` | [Cutout Tool](cutout-tool.md) | Selection、基础 Shape 构建与 Blender 对象创建 |
|
||||
| `operators/image_edit_tool/` | [Image Edit Tool](image-edit-tool.md) | Frame 合成、Mask Alpha 编辑与 Rectify 透视校正 |
|
||||
| `operators/debug.py` | [Debug Operator](debug.md) | 模型接口与原始产物验证 |
|
||||
| `operators/remove_background.py` | [Remove Background](remove-background.md) | BEN2 去背与结果替换 |
|
||||
| `operators/upscale.py` | [Upscale](upscale.md) | 当前 Image 的 4× 高清与输入长边检查 |
|
||||
|
||||
`operators/__init__.py` 聚合注册类,扩展入口按顺序注册并逆序注销。
|
||||
类型在 `operators/__init__.py` 和扩展 `__init__.py` 汇总注册。AI Operator 使用普通字典提交任务,并在主线程响应中读取文件和修改 Blender 数据。
|
||||
|
||||
23
docs/internals/operators/mask.md
Normal file
23
docs/internals/operators/mask.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Mask Alpha 编辑
|
||||
|
||||
`operators/image_edit_tool/mask.py` 定义 `EditImageAlpha` 和 `MaskTool`,使用 `common/viewport.py` 的 `ImageGesture` 管理 Lasso、Brush 与 Polyline。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[冻结 Scene 的 Gesture、Mode、Radius] --> B[屏幕手势与 Overlay]
|
||||
B --> C[完成后投影并生成覆盖 M]
|
||||
C --> D[读取源 RGBA]
|
||||
D --> E[按模式计算 Alpha]
|
||||
E --> F[保留 RGB 和源画布尺寸]
|
||||
F --> G[替换 Packed Image 并提交一个 Undo]
|
||||
```
|
||||
|
||||
| 模式 | 源 Alpha A 与覆盖 M 的结果 |
|
||||
| --- | --- |
|
||||
| Set | `A × M` |
|
||||
| Add | `max(A, M)` |
|
||||
| Subtract | `A × (1 − M)` |
|
||||
|
||||
Brush 拖动时维护简化屏幕路径、圆形印记与连接条的并集预览;释放时一次生成图片覆盖。Polyline 使用逐点路径,至少三个点后通过闭合圈、双击或 Enter 完成,Backspace 删除末点。
|
||||
|
||||
结果保留 Empty 的身份、矩阵、显示尺寸和偏移。全透明结果有效,取消或切换工具清理未提交的 Overlay;共享 Image 通过统一替换入口分流。
|
||||
17
docs/internals/operators/rectify.md
Normal file
17
docs/internals/operators/rectify.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Rectify 透视校正
|
||||
|
||||
`operators/image_edit_tool/rectify.py` 定义 `RectifyImagePerspective` 与 `RectifyTool`,将图片内的四角区域校正为矩形。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[点选四角] --> B[规范为凸 Quad 并检查图片交叠]
|
||||
B --> C[512×512 预览与宽高比调整]
|
||||
C --> D[按像素密度和输出上限确定尺寸]
|
||||
D --> E[分块 Homography 与预乘 Alpha 插值]
|
||||
E --> F[按可见 Alpha 裁剪]
|
||||
F --> G[映射 placement bounds 并替换 Packed Image]
|
||||
```
|
||||
|
||||
四角允许任意点击顺序,退化或非凸输入在提交前校验。预览根据源颜色空间配置显示,像素重采样复用 `common/image.py`。输出最长边上限 8192,总像素上限 16,777,216。
|
||||
|
||||
`replace_empty_image()` 根据校正区域的 placement bounds 调整画框,保留源 Image 名称、对象身份和共享引用语义。
|
||||
@@ -1,44 +1,21 @@
|
||||
# Upscale
|
||||
|
||||
Upscale 从当前 Image Empty 读取单图或多帧媒体,在输入长边允许时生成 4× RGBA 结果并替换原图。
|
||||
|
||||
## 执行过程
|
||||
`operators/upscale.py` 从当前 Image Empty 生成 2× 结果并替换原图,支持单图、视频与序列帧。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Empty["Image Empty"] --> Gate{"长边小于输入上限?"}
|
||||
Gate -- "否" --> Disabled["禁用或报告错误"]
|
||||
Gate -- "是" --> Prepare["准备媒体输入"]
|
||||
Prepare --> Job["upscale-image Job"]
|
||||
Job --> RGB["分块执行 4× RGB 推理"]
|
||||
Job --> Alpha["独立缩放 Alpha"]
|
||||
RGB --> Merge["合并 RGBA"]
|
||||
Alpha --> Merge
|
||||
Merge --> Replace["导入并替换当前 Image"]
|
||||
A[检查源图长边小于 AI 输入上限] --> B[冻结 Preferences 模型与尺寸设置]
|
||||
B --> C[准备媒体输入]
|
||||
C --> D[upscale-image 逐帧处理]
|
||||
D --> E{所选模型}
|
||||
E -- Real-ESRGAN 或 SAFMN --> F[256×256 tile 与边缘上下文执行 4× RGB 推理]
|
||||
E -- PiSA-SR --> G[分阶段生成推理,细节强度 0.7]
|
||||
F --> H[统一缩放到源图 2×]
|
||||
G --> H
|
||||
H --> I[原 Alpha 独立 Lanczos 缩放并回填]
|
||||
I --> J[写出 upscale 序列并替换 Image]
|
||||
```
|
||||
|
||||
模型只处理 RGB,Alpha 单独放大后再合并。Blender 侧在提交前检查 **Maximum AI Input Size**,Server 模型层也应用同一个输入上限。
|
||||
`server/models/upscale.py` 统一处理输入上限、模型分派和最终 RGB/Alpha 尺寸。`onnx_upscale.py` 处理常规 4× 模型,`onnx_pisa_sr.py` 处理 PiSA-SR。多帧与多 tile 只在最后一次运行请求内存收缩。
|
||||
|
||||
## 节点说明
|
||||
|
||||
### 检查尺寸并准备输入
|
||||
|
||||
入口位于 `operators/upscale.py`。`UpscaleImage.poll()` 读取 `preferences.py` 中的 **Maximum AI Input Size**;当前 Image 长边必须严格小于该值。菜单显示的预计尺寸来自同一文件的 `upscale_output_size()`。
|
||||
|
||||
执行时,`upscale.py` 冻结当前模型和输入上限,并通过 `common/image.py` 准备单图、视频或序列输入。Packed Image 与内存修改结果会导出临时文件,多帧输入保留帧起点和抽帧设置。
|
||||
|
||||
### 分块放大 RGB
|
||||
|
||||
`UpscaleImage` 提交 `upscale-image` Job。`server/jobs/upscale.py` 展开媒体帧并逐帧调用模型;`server/models/upscale.py` 负责输入约束、RGB/Alpha 分离和结果合并,`server/models/onnx_upscale.py` 以 256 × 256 分块执行 4× ONNX 推理并带入相邻上下文。
|
||||
|
||||
`server/media/resolution.py` 统一解释输入长边上限。Blender 侧要求源图长边小于该值,结果长边小于该值的四倍。
|
||||
|
||||
### 合并 Alpha
|
||||
|
||||
`server/models/upscale.py` 使用 Lanczos 将 Alpha 放大到目标尺寸,再与 4× RGB 合并为 RGBA。Provider 资源不足时,Server 释放当前 Upscale Session,并返回降低输入上限或改用 CPU 的提示。
|
||||
|
||||
### 导入并替换结果
|
||||
|
||||
响应阶段回到 `operators/upscale.py`,通过 `common/image.py` 读取 `upscale.*` 结果序列并替换当前 Empty。
|
||||
|
||||
后续 Upscale 和 Depth Plane 都以当前结果 Image 为输入。
|
||||
Provider 资源错误会释放当前 Upscale Session,并返回降低输入上限或切换 CPU 的提示。响应通过 `common/image.py` 加载、Pack、保留图像共享与帧设置,并提交 Undo。
|
||||
|
||||
Reference in New Issue
Block a user