Files
AnyImage/tests/test_node_asset_contracts.py
nan 89087fc2e2 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.
2026-09-09 21:24:57 +08:00

146 lines
5.9 KiB
Python

import bpy
from node_asset_test_support import validate_node_structure
from anyimage.common.material import (
DEPTH_MATERIAL_NODE_GROUP_NAME,
IMAGE_MATERIAL_NODE_GROUP_NAME,
)
from anyimage.common.node import load_node_group, node_asset_path
from anyimage.operators.cutout_tool.shape import (
CUTOUT_NODE_GROUP_NAMES,
)
DEPTH_PLANE_NODE_GROUP_NAME = "O Image Depth Plane"
RELIEF_PLANE_NODE_GROUP_NAME = "O Image Relief Plane"
def input_names(group):
return [
item.name
for item in group.interface.items_tree
if item.item_type == "SOCKET" and item.in_out == "INPUT"
]
def test_saved_asset_contracts():
bpy.ops.wm.read_factory_settings(use_empty=True)
image_plane = load_node_group("O Image Plane")
depth_plane = load_node_group(DEPTH_PLANE_NODE_GROUP_NAME)
relief_plane = load_node_group(RELIEF_PLANE_NODE_GROUP_NAME)
image_layer = load_node_group(IMAGE_MATERIAL_NODE_GROUP_NAME)
depth_layer = load_node_group(DEPTH_MATERIAL_NODE_GROUP_NAME)
shadeless = load_node_group("O Shadeless")
cutout_groups = {
name: load_node_group(name) for name in set(CUTOUT_NODE_GROUP_NAMES.values())
}
expected_names = {
"O Image Plane",
DEPTH_PLANE_NODE_GROUP_NAME,
RELIEF_PLANE_NODE_GROUP_NAME,
IMAGE_MATERIAL_NODE_GROUP_NAME,
DEPTH_MATERIAL_NODE_GROUP_NAME,
"O Shadeless",
*CUTOUT_NODE_GROUP_NAMES.values(),
}
with bpy.data.libraries.load(str(node_asset_path()), link=False) as (source, _):
assert set(source.node_groups) == expected_names
assert image_plane.name == "O Image Plane"
assert image_plane.bl_idname == "GeometryNodeTree"
assert image_plane.is_modifier
assert input_names(image_plane) == [
"Geometry",
"Subdivide",
"Thickness",
]
for group, normal_controls in ((depth_plane, ["Normal Smooth", "Smooth Weight", "Validity Threshold"]), (relief_plane, [])):
assert group.bl_idname == "GeometryNodeTree" and group.is_modifier
assert input_names(group) == [
"Geometry", "Subdivide", "Thickness", "Depth Scale",
*(["Smooth", "Valid Only"] if group == depth_plane else []), "Reference Depth",
*normal_controls, "Uniform Scale", "Depth Image",
]
inputs = {item.name: item for item in group.interface.items_tree
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
assert inputs["Thickness"].default_value == 0
assert inputs["Thickness"].subtype == inputs["Reference Depth"].subtype == "DISTANCE"
assert inputs["Subdivide"].default_value == 6
assert all(item.structure_type == "SINGLE" for item in inputs.values())
panels = [item for item in group.interface.items_tree if item.item_type == "PANEL"]
assert [panel.name for panel in panels] == ["Options", "Data"]
assert [item.name for item in panels[0].interface_items] == ["Reference Depth", *normal_controls]
assert [item.name for item in panels[1].interface_items] == ["Uniform Scale", "Depth Image"]
assert all(panel.default_closed for panel in panels)
assert all(item.hide_in_modifier for item in panels[1].interface_items)
assert image_layer.bl_idname == "ShaderNodeTree"
assert input_names(image_layer) == [
"Color",
"Alpha",
"Normal",
"Bump Scale",
"Alpha Fix",
"Normal Scale",
"Object Space",
]
assert depth_layer.bl_idname == "ShaderNodeTree"
adjustment = next(
item for item in image_layer.interface.items_tree
if item.item_type == "SOCKET" and item.in_out == "INPUT"
and item.name == "Alpha Fix"
)
assert adjustment.default_value == 0.0
assert (adjustment.min_value, adjustment.max_value, adjustment.subtype) == (0, 1, "FACTOR")
image_output = next(node for node in image_layer.nodes if node.type == "GROUP_OUTPUT")
alpha_curve = image_output.inputs["Alpha"].links[0].from_node
assert alpha_curve.bl_idname == "ShaderNodeFloatCurve"
assert alpha_curve.inputs["Value"].links[0].from_socket.name == "Alpha"
assert alpha_curve.inputs["Factor"].links[0].from_socket.name == "Alpha Fix"
panels = [item for item in image_layer.interface.items_tree if item.item_type == "PANEL"]
assert len(panels) == 1 and panels[0].name == "Options" and panels[0].default_closed
assert [item.name for item in panels[0].interface_items] == ["Alpha Fix", "Normal Scale", "Object Space"]
alpha_points = alpha_curve.mapping.curves[0].points
assert len(alpha_points) == 4
for point, expected in zip(alpha_points, ((0, 0), (0.8, 0), (0.925, 0.925), (1, 1))):
assert all(abs(actual - value) < 1e-6 for actual, value in zip(point.location, expected))
assert input_names(depth_layer) == [
"Color",
"Alpha",
"Depth",
"Bump Scale",
]
assert shadeless.name == "O Shadeless"
assert shadeless.bl_idname == "ShaderNodeTree"
assert input_names(shadeless) == ["Color"]
assert set(cutout_groups) == set(CUTOUT_NODE_GROUP_NAMES.values())
for group in cutout_groups.values():
assert group.bl_idname == "GeometryNodeTree"
assert group.is_modifier
assert input_names(group)[0] == "Geometry"
public_groups = {
image_plane,
depth_plane,
relief_plane,
image_layer,
depth_layer,
shadeless,
*cutout_groups.values(),
}
loaded = {*public_groups, *cutout_groups.values()}
for group in loaded:
if group.bl_idname == "GeometryNodeTree":
validate_node_structure(group)
assert all(group.use_fake_user for group in public_groups)
assert all(group.users > 0 for group in cutout_groups.values())
if bpy.app.version >= (5, 0, 0):
assert all(group.library is not None for group in loaded)
print(
f"Validated Blender {bpy.app.version_string}: "
+ ", ".join(sorted(group.name for group in loaded))
)