Files
AnyImage/tests/tools/nodes/test_inventory.py
nan ab282e4fdf feat: Implement depth cutout symmetry and associated functionalities
- Introduced a new proposal for depth cutout symmetry, allowing for the reuse of depth replacement and geometry operations.
- Added requirements for maintaining default thickness values for cutout nodes and ensuring proper initialization during object creation.
- Enhanced symmetry node to handle existing geometry, including alignment, mirroring, and boundary filling.
- Implemented fill smoothing to control the geometry of generated walls while preserving original surfaces.
- Created tests to validate the functionality of the new symmetry features against baseline geometry.
- Updated existing assets and node groups to support the new symmetry capabilities without introducing additional dependencies.
2026-09-14 14:58:48 +08:00

167 lines
7.8 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", "Smooth", "Smooth Weight",
"Depth Mask", "Mask Threshold", "Boundary Smooth",
},
"O Image Relief Plane": {"Subdivide", "Thickness", "Smooth", "Smooth Weight"},
"O Image Cutout": {"Thickness", "Smooth", "Smooth Weight"},
"O Image Depth Cutout": {
"Thickness", "Depth Split", "Smooth", "Smooth Weight",
"Boundary Smooth", "Normal Smooth", "Front Inflation",
},
"O Image Cutout Symmetry": {"Symmetry Direction", "Depth Offset", "Fill Sides", "Fill Smooth"},
"O Image Depth Panorama": {
"Subdivide", "Depth Scale", "Depth Split", "Smooth", "Smooth Weight",
"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
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 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,
})
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
if group.name == "O Image Depth Cutout":
assert all(item.default_value == 0 for item in sockets if item.name == "Thickness")
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"
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