feat: Add new Geometry Nodes groups for image relief and shadeless shader

- Implemented the "O Image Relief Plane" Geometry Nodes group to create a fixed-base image relief using depth images and various parameters.
- Created the "O Shadeless" shader node group to display color without scene lighting while preserving shadows and reflections.
- Introduced a preview feature for node groups, allowing for interactive visualization of node arrangements in an HTML format.
- Added a template HTML file for rendering node previews with customizable views and controls.
This commit is contained in:
2026-09-09 21:24:57 +08:00
parent f5aff50b13
commit 89087fc2e2
118 changed files with 5635 additions and 3130 deletions

View File

@@ -34,7 +34,7 @@ Blender Stub 共用 `blender_test_support.py`;Server 和实际 Blender 几何
`tools/pack.py` 从 `pyproject.toml` 读取依赖,按平台与 Blender Python 版本解析 wheel,使用 Blender 提供的 NumPy。产物命名为 `AnyImage.v<version>.<platform>.zip`,写入 `dist/`。本地 BlendJob wheel 内容变化后运行 `uv lock --refresh`,并核对锁文件、Manifest 与打包测试。
`tools/sync_models.py` 根据模型目录选择上游 ONNX 或本地导出流程;`model_exports.py` 处理对应 PyTorch 权重导出。文件校验依据声明大小和 SHA-256,上传使用逐文件复制。`--prepare-only` 用于本地准备。
`tools/models/cli.py` 根据模型目录选择上游 ONNX 或本地导出流程;`tools/models/exports.py` 处理对应 PyTorch 权重导出。文件校验依据声明大小和 SHA-256,上传使用逐文件复制。`--prepare-only` 用于本地准备。
## CI 与文档导航

View File

@@ -1,149 +1,51 @@
# 节点资产
生产节点组由脚本构建到 `src/anyimage/assets/O_AnyImage.blend`;运行时只加载,不创建节点组。
## 构建节点资产
## 构建
生产节点组由 `uv run build_node` 构建到 `src/anyimage/assets/O_AnyImage.blend`
在项目根目录执行:
节点组文件在 `tools/nodes/groups`
```bash
uv run build_node
```
节点组规则:
脚本依次构建资产、运行三项 Blender 验证和完整测试,任一步失败即退出。
- Group Input 按区域重复放置,Named Attribute 等字段入口就近读取;隐藏所有未使用输出,避免从远处拉线
- 跨区域暂存值用 Store Named Attribute 写入 `o_*` 命名空间,按用途分配前缀并明确 FACE、CORNER、POINT 域和存取时机;末尾用一个 Wildcard 模式的 Remove Named Attribute 清理该前缀,例如 `o_depth_*`,保留用户属性和 UV
- Capture Attribute 适合局部字段固定;若属性连线跨越多个步骤,改为命名属性,让计算分支独立展开
- 保留节点默认 `name` 和标题,不用 `label` 覆盖,不用 String 节点注解。Frame 仅用于确有包裹语义的区域
- 全接线的简单数学运算可折叠;需要阅读的常数、运算域和模式保持可见
- 修改器 Geometry Nodes 的输入 socket 默认使用 `SINGLE`
- socket `subtype` 按业务语义设置:长度用 `DISTANCE`,`0–1` 比例用 `FACTOR`,`0–100` 用 `PERCENTAGE`;无对应范围语义时保留默认
- 接口或布局变更须验证 socket 顺序与结构、未使用输出隐藏及几何求值;纯布局变更比较运算、默认值和实际连接,确认功能不变
脚本按 `--blender`、环境变量 `BLENDER_BIN`、项目 `.env`、`PATH` 的顺序查找 Blender。无法自动找到时,在项目根目录的 `.env` 中配置:
```dotenv
BLENDER_BIN="/absolute/path/to/blender"
```
`.env` 已被 Git 忽略。临时指定路径或跳过 Python 测试:
```bash
uv run build_node --blender "/path/to/blender"
uv run build_node --skip-tests
```
## 资产清单
| 节点组 | 构建模块 |
| --- | --- |
| `O Image Plane` | `image_plane.py` |
| `O Image Depth Plane` | `depth_plane.py` |
| `O Image Layer`、`O Image Depth Layer` | `material_layers.py` |
| `O Image Depth Plane` | `image_depth_plane.py` |
| `O Image Relief Plane` | `image_relief_plane.py` |
| `O Image Layer` | `image_layer.py` |
| `O Image Depth Layer` | `image_depth_layer.py` |
| `O Shadeless` | `shadeless.py` |
| `O Image Cutout`、`O Image Depth Cutout`、`O Image Depth Balloon` | `cutout.py` |
| `O Image Cutout` | `image_cutout.py` |
| `O Image Depth Cutout` | `image_depth_cutout.py` |
| `O Image Depth Balloon` | `image_depth_balloon.py` |
构建从空 Factory 文件生成全部节点组,关闭 `.blend1` 备份,并覆盖唯一发布资产。
## 源码职责
## 构建资产脚本
`tools/node_assets/` 的模块按资产、几何计算、布局和验证组织:
`tools/nodes/build.py` 按依赖顺序构建资产,调用 `arrangement/` 统一排列几何节点组后保存。`common/` 提供共用构建函数,`preview/` 导出交互预览。
| 模块 | 职责 |
| --- | --- |
| `asset_library.py` | 构建全部节点组并保存唯一资产 |
| `image_plane.py`、`depth_plane.py` | 普通平面与 Relief / Camera 深度平面 |
| `material_layers.py`、`shadeless.py` | 图像材质层与无光照着色 |
| `cutout.py`、`cutout_shapes.py` | Cutout 修改器接口与各预设几何 |
| `depth_plane_camera.py`、`depth_surface.py` | Camera 平面、共享投影、深度分离与厚度 |
| `cutout_volume.py`、`cutout_rounding.py` | 平滑中心曲面与轮廓圆润 |
| `cutout_inflation.py`、`cutout_shell.py` | 正面膨胀、法线和正背面位置 |
| `plane_layouts.py`、`depth_plane_layout.py` | 平面资产与 Camera 分支布局 |
| `cutout_layout.py`、`cutout_shape_layouts.py`、`depth_cutout_layout.py` | Cutout 阶段布局、坐标与跨区域几何引用 |
| `node_graph.py`、`node_layout.py` | 共用字段节点、平滑构建、布局与边界检查 |
| `asset_validation.py` | 资产清单、接口、结构与布局检查 |
| `image_plane_validation.py`、`cutout_validation.py` | 平面和 Cutout 几何求值验证 |
保存后,`check.py` 在独立 Blender 进程中重新加载资产并做基本几何求值。接口、默认值和详细几何行为由 `tests/` 检查。
`tools/build_node.py` 依次启动资产构建与三个验证脚本。运行时加载入口位于 `common/node.py`。
Depth Cutout 的布局由 `depth_cutout_layout.py` 编排:Split 属性采样位于主链上方,原始几何旁路沿下方的 Reroute 通道汇入 Switch;长距离投影引用共用分段干线,并在 Uniform 厚度与零厚度输出附近分叉。几何统计支线就近放置在其使用区域。
三个 Cutout 节点组直接作为修改器使用,统一接收局部 XY 平面,以 `-Z` 表示向图片内部的方向。每组在塑形与平滑后处理 **Inward Axis**:默认 `-Z` 直接输出原生几何,`+X` 分支执行一次 Z 到 X 的轴转换;运行时对象矩阵使用逆变换补偿,保证两种模式落在相同世界空间位置。
`O Image Depth Plane`、`O Image Depth Cutout` 与 `O Image Depth Balloon` 使用 **Reference Depth** 作为采样深度的运算基准。
### Cutout 预设与厚度
| 创建入口 | 修改器节点组 | 默认设置 |
| --- | --- | --- |
| Flat | O Image Cutout | Balloon,Thickness 0 |
| Solid(默认) | O Image Cutout | Balloon,Thickness 1 |
| Depth Solid | O Image Depth Cutout | Balloon,Thickness 1 |
| Depth Balloon | O Image Depth Balloon | Thickness 1,Double Sided 开启 |
前两种节点组提供 **Mode**,默认显示 Balloon。两个独立输入均显示为 **Thickness**:第一个是 Balloon 的无量纲倍率,第二个是 Uniform 的距离,各自保存数值。普通 Cutout 在零厚度时输出平面,深度 Cutout 输出深度表面。全部基础网格包含 POINT 域的 `o_balloon`,Flat 可直接调整为 Solid。
深度 Cutout 的 Balloon 厚度由 `cutout_volume.py` 构造平滑中心,再由 `cutout_rounding.py` 处理圆润轮廓与凸起方向。Options 中位于 Front Inflation 前的 **Edge Round** 默认 0.2,正背面共用 `1 − smootherstep(0, max(o_balloon), o_balloon)` 衰减权重。正面向中心加 Balloon 的轮廓靠拢,背面按 Balloon 值补充饱满度。Options 中的 **Normal Smooth** 默认 50:在中心曲面上平滑并归一化法线,背面沿该方向凸起,正面按边缘权重过渡。两个边缘范围及法线影响固定为 1。
中心位置以投影正面 Z 减去 `o_balloon` 初始化,固定轮廓与切口,在内部通过一个 Blur Attribute 执行 256 次邻域平滑,边界 Weight 为 0。圆润处理前,深凹处保留至少 `0.05 × o_balloon` 的厚度。轮廓零厚度点按来源身份合并,暂存字段使用 POINT 域 `o_balloon_surface_*` 并在输出前清理。`cutout_shell.py` 让薄厚度的 Balloon 从正面沿法线向内生长,再按平滑过渡达到 Thickness 为 1 的圆润体积;大于 1 时继续沿用该体积公式。Uniform 沿正面法线偏移指定距离,同样受 Normal Smooth 控制。Thickness 为 0 时返回原深度表面。整体 Smooth 复用公共构建函数,三个 Cutout 节点组的 Smooth 默认 0,Smooth Weight 默认 1。
`cutout_inflation.py` 在邻域中拟合深度已有的结构性鼓起,Options 中 **Front Inflation** 默认 1。Balloon 令结构量为 k、Thickness 与 Front Inflation 的乘积为 a,正面增加 `o_balloon × (cbrt((k+0.25)³+(a+0.25)³−0.25³)−k−0.25)`,通过有理化形式求值以保持小厚度精度。已有 1 倍鼓起、两个参数均为 1 时,额外增加约 0.323 倍 Balloon。Uniform 直接增加 `Thickness × Front Inflation`,不受结构量或轮廓权重限制;Front Inflation 为 1 时,正面边缘和中心的增量均等于背面的厚度距离。两种模式厚度为 0 时仍返回原表面,单层侧壁直接连接正背面。
**Normal Smooth** 同时控制正面、背面与 Uniform 的法线;正面膨胀在基础法线平滑之后使用 5.12 倍迭代范围,默认 50 对应额外 256 次,保持大膨胀量下的连续方向。设置为 0 时两阶段均停止平滑。Front Inflation 为 0 时关闭正面额外鼓起。
Normal Map 勾选控制 Flat 与 Solid 的 TANGENT 法线;Depth Solid 固定生成 OBJECT 法线,Depth Balloon 固定生成 TANGENT 法线。材质读取 `o_normal_scale`:普通 Cutout 和 Depth Balloon 使用 POINT 域的轮廓适配权重;Depth Cutout 使用 FACE 域的正面标记,让背面与切口保留几何法线。该属性属于输出材质协议,随 UVMap 与 o_balloon 保留。
Options 依次排列 Cleanup Threshold、Split Threshold、Reference Depth、Edge Round、Front Inflation、Normal Smooth、Smooth Weight;各节点组仅显示适用项,Depth Balloon 的 Depth Direction 位于末尾。
### Depth Cutout 分离与厚度
Options 中的 **Split Threshold** 范围为 `0–1`,默认 `0` 关闭分离,数值越大越容易分离。节点比较 `原始 Z 差 × Uniform Scale × Split Threshold > 原始面中心距离 × 2.5`;`0.5` 对应深度坡度阈值 `5`。**Uniform Scale** 将原始深度标定到网格单位,**Depth Scale** 仅控制最终置换幅度。
```mermaid
flowchart TD
A[基础网格与深度图片] --> D[共用 Cleanup 按原平面岛面积清理]
D --> B[面中心采样与共享边判别]
B --> C[存入面角命名属性并 Split Edges]
C --> E[切口顶点沿原相机射线采用所属侧深度]
E --> J[固定顶点数据并投影]
J --> L[选择平滑 Balloon 或 Uniform 厚度]
L --> F[生成正背面与单层四边侧壁]
F --> G[按原顶点及正背面身份连接]
G --> H[恢复场景坐标]
H --> K[移除 o_depth_* 暂存属性]
K --> I[边缘与整体平滑及方向变换]
```bash
uv run build_node --blender "/path/to/blender"
uv run build_node --skip-tests
```
三个节点组在塑形前复用 Cleanup,按原平面岛的总面积清理小区域。Depth Cutout 随后执行 Split,保留分离产生的小片;Split 关闭时使用原顶点采样。
脚本按 `--blender`、环境变量 `BLENDER_BIN`、项目 `.env`、`PATH` 的顺序查找 Blender。无法自动找到时,在项目根目录的 `.env` 中配置:
未关联选中切边的顶点使用原始 XYZ。切口顶点以 `原 XYZ × 所属侧 Z / 原 Z` 保持原相机射线,随后执行既有 Uniform Scale、Reference Depth 和 Depth Scale 投影。
厚度前固定顶点数据,使正背面和侧壁使用相同位置。连接时以原顶点编号和正背面身份构造临时整数坐标,合并同一身份的副本后恢复场景坐标,保持近距离分离区域各自独立。实现位于 `depth_surface.py`,由 `cutout.py` 构建进共享节点资产。
暂存值通过 Store Named Attribute 写入保留前缀 `o_depth_`:面中心与相机采样使用 FACE,所属侧相机采样与切口标记使用 CORNER,最终相机坐标、表面位置与源顶点编号使用 POINT。计算区域就近使用 Named Attribute,末尾用一个 Wildcard 模式的 Remove Named Attribute 匹配 `o_depth_*`,输出保留 UVMap 与用户属性。
### 整图 Depth Plane
`O Image Depth Plane` 提供默认 **Relief** 与 **Camera** 模式,共用完整矩形网格和 `depth.exr`。RGB 保存相机 XYZ,Alpha 保存有效性。Relief 与 Depth Balloon 读取 B/Z,Camera 与 Depth Cutout 读取 XYZ。用户控制为 **Mode**、**Subdivide**、**Thickness**、**Depth Scale** 和 Options 中的 **Reference Depth**、**Normal Smooth**;**Depth Image** 与 **Uniform Scale** 为隐藏数据输入。
```mermaid
flowchart TD
A[O Image Plane 零厚度矩形细分] --> B[按 Depth Scale 插值相机 XY 与相对 Z]
B --> C{Thickness}
C -- 0 --> D[单层曲面]
C -- 大于 0 --> E[沿平滑法线向内生成背面并连接边界]
D --> F[清理临时属性并输出]
E --> F
```dotenv
BLENDER_BIN="/absolute/path/to/blender"
```
两个独立输入均显示 Thickness,默认均为 0 m:第一个控制 Relief 的固定底面基础厚度,第二个控制 Camera 的向内壳厚度。上图描述 Camera 分支:Depth Scale 为 0 时恢复平面,1 时使用完整相机投影;正面 POINT 法线按 Normal Smooth 平滑并归一化,再按 Thickness 向内生成背面。Normal Smooth 范围 0–50、默认 50,仅影响 Camera。模式切换保留各自厚度值并复用图片。Camera 分支位于 `depth_plane_camera.py`,投影与厚度共用 `depth_surface.py`,布局由 `depth_plane_layout.py` 编排。
### 几何节点布局
全部五个几何节点组使用连续横排的几何主轴。主轴可适当宽松,支线内部紧凑,同一目的的计算聚在同一侧,内部间距明显小于与其他部分的距离;独立用途可在两侧错落展开。长算式横排,末端就近汇入主轴。Join Geometry、Menu Switch、Combine XYZ 的同级独立分支末端排成一列并对齐输出端,菜单分支按输入顺序上下排列,共享上游留在公共位置。必要的向左引用直接连接。Group Input 与 Named Attribute 就近放置,全接线的简单运算可折叠,常数和域设置保持可见。
`plane_layouts.py` 组织平面资产布局,`cutout_shape_layouts.py` 和 `depth_cutout_layout.py` 组织 Cutout 的局部计算阶段;`cutout_layout.py` 将阶段布局放入三个修改器,`node_layout.py` 验证节点边界与同级输入对齐。布局验收包含实际节点编辑器预览及几何求值。
## 修改节点组
1. 修改 `tools/node_assets/` 中对应资产、计算或布局模块;
2. 更新对应验证;
3. 运行一键构建脚本;
4. 搜索并清理旧名称、接口和路径;
5. 检查 `.blend`、代码、测试和文档差异。
接口、节点、连线、默认值或布局变化时必须重建。只修改运行时 Python、Markdown 或打包逻辑时无需重建。
`O_AnyImage.blend1` 等备份不应提交或打包。