- Added a new proposal for boundary smoothing in the weight cutout process, differentiating between Split edges and Outline smoothing requirements. - Introduced a new capability `cutout-boundary-smoothing` to control smoothing strength and default values. - Updated specifications to define the behavior of boundary-specific smoothing strength and default settings. - Created tasks to verify the implementation of boundary behavior and asset updates. - Developed comprehensive tests for depth symmetry and normal scaling to ensure expected behavior and performance. - Introduced new attributes for normal mapping to facilitate persistent face attributes sharing between depth symmetry and image materials. - Built a new node group for O Image Depth Symmetry, integrating the new smoothing capabilities and ensuring compatibility with existing workflows.
166 lines
7.7 KiB
Python
166 lines
7.7 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 Depth Symmetry": {"Thickness", "Smooth", "Smooth Weight", "Boundary 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 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
|