241 lines
12 KiB
Python
241 lines
12 KiB
Python
"""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", "Smooth",
|
|
},
|
|
"O Image Depth Panorama": {
|
|
"Subdivide", "Depth Scale", "Depth Split", "Dome Radius",
|
|
"Depth Mask", "Mask Threshold", "Boundary Smooth",
|
|
},
|
|
"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)
|
|
dome = next(item for item in input_items if item.name == "Dome Radius")
|
|
assert (dome.min_value, dome.max_value) == (1, 100)
|
|
assert dome.default_value == 50
|
|
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")
|
|
smooth = next(item for item in input_items if item.name == "Smooth")
|
|
assert (offset.min_value, offset.max_value) == (-10.0, 10.0)
|
|
assert (scale.min_value, scale.max_value) == (0.0, 2.0)
|
|
assert (smooth.min_value, 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_anyimage_symmetry_weld": ("BOOLEAN", "POINT"),
|
|
"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
|