"""Check the saved library's public inventory and persistence.""" import bpy import pytest from anyimage.common.node import node_asset_path from tools.nodes.check import GEOMETRY_NAMES def test_saved_assets_evaluate(): from tools.nodes.check import check_asset_library bpy.ops.wm.read_factory_settings(use_empty=True) check_asset_library(node_asset_path()) def test_saved_parameter_descriptions_match_definitions(): from tools.nodes.build import build_node_groups parameters = { "O Image Plane": {"Subdivide"}, "O Image Depth Plane": { "Subdivide", "Thickness", "Depth Split", "Depth Mask", "Mask Threshold", "Boundary Smooth", }, "O Image Relief Plane": { "Subdivide", "Thickness", "Depth Direction", "Depth Offset", }, "O Image Cutout": {"Thickness"}, "O Image Depth Cutout": { "Thickness", "Depth Split", "Boundary Smooth", "Normal Smooth", "Front Inflation", }, "O Image Cutout Symmetry": { "Direction", "Offset", "Scale", "Fill Sides", "Fill Smooth", }, "O Image Depth Panorama": { "Subdivide", "Depth Scale", "Depth Split", "Depth Mask", "Mask Threshold", "Boundary Smooth", "Invalid Distance", }, "O Image Layer": {"Alpha Fix", "Normal Scale", "Bump Scale", "Object Space"}, "O Image Depth Layer": {"Bump Scale"}, } def descriptions(groups): result = {} for group in groups: if group.name not in parameters: continue sockets = [ item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" and item.name in parameters[group.name] ] assert {item.name for item in sockets} == parameters[group.name] assert all(item.description for item in sockets), group.name input_names = { item.name for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" } input_items = [ item for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT" ] if group.name in { "O Image Cutout", "O Image Depth Cutout", "O Image Depth Plane", "O Image Depth Panorama", "O Image Relief Plane", }: assert "Smooth" not in input_names assert "Smooth Weight" not in input_names if "Mask Threshold" in parameters[group.name]: threshold = next(item for item in input_items if item.name == "Mask Threshold") assert (threshold.min_value, threshold.max_value) == pytest.approx((0.0, 0.99)) if group.name in { "O Image Depth Cutout", "O Image Depth Plane", "O Image Relief Plane", }: options = next( item for item in group.interface.items_tree if item.item_type == "PANEL" and item.name == "Options" ) reference = next(item for item in input_items if item.name == "Reference Depth") assert reference.parent == options siblings = [ item for item in group.interface.items_tree if item.parent == options and item.item_type == "SOCKET" ] assert siblings[-1].name == "Reference Depth" if group.name in {"O Image Depth Plane", "O Image Depth Cutout"}: boundary = next(item for item in sockets if item.name == "Boundary Smooth") assert boundary.default_value == 4 assert boundary.min_value == 0 assert boundary.max_value == 16 assert boundary.parent.name == "Options" if group.name == "O Image Depth Plane": depth_scale = next(item for item in input_items if item.name == "Depth Scale") assert (depth_scale.min_value, depth_scale.max_value) == (0.0, 2.0) if group.name == "O Image Depth Panorama": defaults = {item.name: item.default_value for item in sockets if item.name in {"Subdivide", "Depth Split", "Boundary Smooth"}} assert defaults == pytest.approx({ "Subdivide": 8, "Depth Split": 0.1, "Boundary Smooth": 2, }) boundary = next(item for item in input_items if item.name == "Boundary Smooth") assert (boundary.min_value, boundary.max_value) == (0, 16) if group.name == "O Image Relief Plane": names = [item.name for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT"] start = names.index("Thickness") assert names[start:start + 4] == [ "Thickness", "Depth Direction", "Depth Offset", "Depth Scale", ] thickness = next(item for item in sockets if item.name == "Thickness") direction = next(item for item in sockets if item.name == "Depth Direction") offset = next(item for item in sockets if item.name == "Depth Offset") assert offset.default_value == pytest.approx(0.0) assert (offset.min_value, offset.max_value) == (-10.0, 10.0) assert offset.subtype == "DISTANCE" assert direction.parent == thickness.parent == offset.parent depth_scale = next(item for item in input_items if item.name == "Depth Scale") assert (depth_scale.min_value, depth_scale.max_value) == (0.0, 2.0) if "Depth Mask" in parameters[group.name]: names = [item.name for item in group.interface.items_tree if item.item_type == "SOCKET" and item.in_out == "INPUT"] assert names.index("Depth Mask") == names.index("Depth Split") + 1 if group.name in {"O Image Cutout", "O Image Depth Cutout"}: mode = next(node for node in group.nodes if node.bl_idname == "GeometryNodeMenuSwitch" and node.inputs["Menu"].links[0].from_socket.name == "Mode") assert [item.name for item in mode.enum_items] == ["Balloon", "Shell"] thickness = [item.description for item in sockets if item.name == "Thickness"] assert len(thickness) == 2 and len(set(thickness)) == 2 thicknesses = [item for item in input_items if item.name == "Thickness"] balloon = next(item for item in thicknesses if item.subtype == "NONE") shell = next(item for item in thicknesses if item.subtype == "DISTANCE") assert (balloon.min_value, balloon.max_value) == (0.0, 2.0) assert (shell.min_value, shell.max_value) == (0.0, 1.0) if group.name == "O Image Depth Cutout": assert all(item.default_value == 0 for item in sockets if item.name == "Thickness") depth_scale = next(item for item in input_items if item.name == "Depth Scale") assert (depth_scale.min_value, depth_scale.max_value) == (0.0, 2.0) assert not {"Outline Depth Fix", "Center Smooth", "Center Subdivide", "Show Center"} & { item.name for item in group.interface.items_tree if item.item_type == "SOCKET" } inflation = next(item for item in sockets if item.name == "Front Inflation") assert inflation.default_value == pytest.approx(0.2) normal_smooth = next(item for item in sockets if item.name == "Normal Smooth") assert normal_smooth.default_value == 64 assert normal_smooth.min_value == 16 assert normal_smooth.max_value == 512 assert normal_smooth.parent.name == "Options" if group.name == "O Image Cutout Symmetry": offset = next(item for item in input_items if item.name == "Offset") scale = next(item for item in input_items if item.name == "Scale") fill_smooth = next(item for item in input_items if item.name == "Fill Smooth") assert (offset.min_value, offset.max_value) == (-10.0, 10.0) assert (scale.min_value, scale.max_value) == (0.0, 2.0) assert (fill_smooth.min_value, fill_smooth.max_value) == (0, 16) result[group.name] = [ (item.identifier, item.name, item.description) for item in sockets ] assert result.keys() == parameters.keys() return result bpy.ops.wm.read_factory_settings(use_empty=True) expected = descriptions(build_node_groups()) bpy.ops.wm.read_factory_settings(use_empty=True) with bpy.data.libraries.load(str(node_asset_path())) as (library, target): target.node_groups = list(library.node_groups) assert descriptions(target.node_groups) == expected @pytest.mark.parametrize("source", ["definition", "asset"]) def test_geometry_nodes_contribute_to_output(source): from tools.nodes.build import build_node_groups bpy.ops.wm.read_factory_settings(use_empty=True) if source == "definition": groups = build_node_groups() else: with bpy.data.libraries.load(str(node_asset_path())) as (library, target): target.node_groups = list(library.node_groups) groups = target.node_groups for group in groups: if group.bl_idname != "GeometryNodeTree": continue pending = [node for node in group.nodes if node.bl_idname == "NodeGroupOutput"] used = set() while pending: node = pending.pop() if node in used: continue used.add(node) pending.extend(link.from_node for socket in node.inputs for link in socket.links) pending.extend(candidate for candidate in group.nodes if getattr(candidate, "paired_output", None) == node) unused = [node.name for node in group.nodes if node not in used and node.bl_idname != "NodeFrame"] assert not unused, (group.name, unused) @pytest.mark.parametrize("source", ["definition", "asset"]) def test_geometry_attribute_contract_includes_nested_groups(source): from tools.nodes.build import build_node_groups bpy.ops.wm.read_factory_settings(use_empty=True) if source == "definition": pending = build_node_groups() else: with bpy.data.libraries.load(str(node_asset_path())) as (library, target): target.node_groups = list(library.node_groups) pending = list(target.node_groups) seen = set() allowed = { "UVMap": ("FLOAT2", "CORNER"), "o_balloon": ("FLOAT", "POINT"), "_o_anyimage_depth_cut": ("BOOLEAN", "CORNER"), "o_depth_rotation": ("FLOAT_VECTOR", "FACE"), "o_depth_face": ("FLOAT", "FACE"), "o_depth_axis": ("FLOAT", "FACE"), } while pending: group = pending.pop() if group in seen or group.bl_idname != "GeometryNodeTree": continue seen.add(group) for node in group.nodes: assert node.bl_idname != "GeometryNodeCaptureAttribute", group.name if node.bl_idname == "GeometryNodeGroup" and node.node_tree: pending.append(node.node_tree) if node.bl_idname != "GeometryNodeStoreNamedAttribute": continue name = node.inputs["Name"].default_value if name == "o_normal_reduction": assert node.data_type == "FLOAT" and node.domain in {"POINT", "FACE"} else: assert name in allowed, (group.name, name) assert (node.data_type, node.domain) == allowed[name] assert {group.name for group in seen} == GEOMETRY_NAMES