Files
AnyImage/tools/node_assets/asset_validation.py
nan 4c902ed7db 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.
2026-09-08 23:24:27 +08:00

154 lines
4.8 KiB
Python

import sys
from pathlib import Path
import bpy
PROJECT_ROOT = Path(__file__).parents[2]
sys.path.insert(0, str(Path(__file__).parent))
sys.path.insert(0, str(PROJECT_ROOT / "src"))
sys.path.insert(
0,
str(next((PROJECT_ROOT / "src" / "anyimage" / "wheels").glob("blendjob-*.whl"))),
)
from node_layout import validate_node_layout
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"
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 main():
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)
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,
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",
]
assert depth_plane.bl_idname == "GeometryNodeTree"
assert depth_plane.is_modifier
assert input_names(depth_plane) == [
"Geometry",
"Mode",
"Subdivide",
"Thickness",
"Thickness",
"Depth Scale",
"Reference Depth",
"Normal Smooth",
"Uniform Scale",
"Depth Image",
]
thickness_inputs = [
item for item in depth_plane.interface.items_tree
if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name == "Thickness"
]
assert len({item.identifier for item in thickness_inputs}) == 2
assert all(item.default_value == 0 and item.subtype == "DISTANCE" for item in thickness_inputs)
depth_panels = [
item for item in depth_plane.interface.items_tree if item.item_type == "PANEL"
]
assert [panel.name for panel in depth_panels] == ["Options", "Data"]
assert [item.name for item in depth_panels[0].interface_items] == [
"Reference Depth", "Normal Smooth"
]
assert [item.name for item in depth_panels[1].interface_items] == [
"Uniform Scale",
"Depth Image",
]
assert all(panel.default_closed for panel in depth_panels)
assert all(item.hide_in_modifier for item in depth_panels[1].interface_items)
depth_subdivide = next(
item
for item in depth_plane.interface.items_tree
if item.item_type == "SOCKET" and item.name == "Subdivide"
)
assert depth_subdivide.default_value == 6
assert image_layer.bl_idname == "ShaderNodeTree"
assert input_names(image_layer) == [
"Color",
"Alpha",
"Normal",
"Normal Scale",
"Object Space",
"Bump Scale",
]
assert depth_layer.bl_idname == "ShaderNodeTree"
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,
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_layout(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))
)
if __name__ == "__main__":
main()