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

71
tools/nodes/check.py Normal file
View File

@@ -0,0 +1,71 @@
"""Reload saved assets and evaluate geometry in the target Blender process."""
from math import isfinite
from pathlib import Path
import bpy
ASSET_PATH = Path(__file__).resolve().parents[2] / "src/anyimage/assets/O_AnyImage.blend"
GEOMETRY_NAMES = {
"O Image Plane", "O Image Depth Plane", "O Image Relief Plane",
"O Image Cutout", "O Image Depth Cutout", "O Image Depth Balloon",
}
SHADER_NAMES = {"O Image Layer", "O Image Depth Layer", "O Shadeless"}
def check_asset_library(asset_path):
"""Check library completeness and finite, nonempty evaluated geometry."""
with bpy.data.libraries.load(str(asset_path), link=True) as (source, target):
assert set(source.node_groups) == GEOMETRY_NAMES | SHADER_NAMES
target.node_groups = list(source.node_groups)
depth_image = bpy.data.images.new("Asset Check Depth", width=8, height=8, float_buffer=True)
depth_image.colorspace_settings.name = "Non-Color"
depth_image.pixels.foreach_set([
value for y in range(8) for x in range(8)
for value in ((x + 0.5) / 8 - 0.5, (y + 0.5) / 8 - 0.5, 2.0, 1.0)
])
for node_group in target.node_groups:
expected_type = "GeometryNodeTree" if node_group.name in GEOMETRY_NAMES else "ShaderNodeTree"
assert node_group.bl_idname == expected_type and node_group.library is not None, node_group.name
if expected_type == "ShaderNodeTree":
continue
mesh = bpy.data.meshes.new("Asset Check Plane")
mesh.from_pydata([(-0.5, -0.5, 0), (0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)], [], [(0, 1, 2, 3)])
uv = mesh.uv_layers.new(name="UVMap")
for loop in mesh.loops:
position = mesh.vertices[loop.vertex_index].co
uv.data[loop.index].uv = (position.x + 0.5, position.y + 0.5)
profile = mesh.attributes.new("o_balloon", "FLOAT", "POINT")
for value in profile.data:
value.value = 0.2
obj = bpy.data.objects.new("Asset Check", mesh)
bpy.context.collection.objects.link(obj)
modifier = obj.modifiers.new("Asset Check", "NODES")
modifier.node_group = node_group
values = {"Subdivide": 0, "Thickness": 0.2, "Reference Depth": 2.0,
"Depth Image": depth_image, "Uniform Scale": 1.0, "Normal Smooth": 0}
for item in node_group.interface.items_tree:
if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name in values:
modifier[item.identifier] = values[item.name]
obj.update_tag(refresh={"DATA"})
bpy.context.view_layer.update()
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
result = evaluated.to_mesh()
try:
assert len(result.vertices) and len(result.polygons), node_group.name
assert all(isfinite(value) for vertex in result.vertices for value in vertex.co), node_group.name
finally:
evaluated.to_mesh_clear()
bpy.data.objects.remove(obj, do_unlink=True)
bpy.data.meshes.remove(mesh)
bpy.data.images.remove(depth_image)
print(f"Checked {len(target.node_groups)} saved node groups in Blender {bpy.app.version_string}")
def main():
bpy.ops.wm.read_factory_settings(use_empty=True)
check_asset_library(ASSET_PATH)
if __name__ == "__main__":
main()