- Unified reference depth calculation to use a single implementation in common/depth.py. - Removed server-side reference depth computation and adjusted depth.json to only include image_size and intrinsics. - Updated depth texture handling to ignore transparent, invalid, and non-positive pixels when calculating reference depth. - Changed reference depth calculation to use the 95th percentile of valid depth values. - Adjusted depth direction fitting to return a flat direction when no samples are available. - Removed cleanup threshold parameters from Cutout and related nodes, simplifying the interface. - Updated tests to verify new reference depth logic and ensure compatibility with existing functionality.
221 lines
11 KiB
Python
221 lines
11 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 Weight",
|
|
"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", "Smooth Weight",
|
|
"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", "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
|
|
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
|
|
if group.name in {"O Image Cutout", "O Image Relief Plane"}:
|
|
assert "Smooth Weight" not in input_names
|
|
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
|