Refactor tests and remove unused imports

- Cleaned up test files by removing unnecessary imports and unused parameters.
- Simplified test functions by eliminating redundant parameterization and consolidating logic.
- Updated test cases to directly use defined values instead of parameterized inputs where applicable.
- Enhanced readability and maintainability of the test code across various test files.
This commit is contained in:
2026-09-17 10:02:05 +08:00
parent ac7f5a7f6d
commit 0aeb61f818
22 changed files with 95 additions and 372 deletions

View File

@@ -238,10 +238,6 @@ def extract_selection_contours(selection_values, max_resolution=512, threshold=0
image_size=source_size,
)
POINT_SPACING = 0.1
def contour_area(points):
import numpy as np
@@ -560,19 +556,6 @@ def crop_plane_bounds(source_object, crop_bounds, source_size):
)
def extract_cutout_contours(
content_values,
sampling_resolution,
threshold=CUTOUT_ALPHA_THRESHOLD,
):
contour_resolution = max(512, min(round(sampling_resolution * 8), 1024))
return extract_selection_contours(
content_values,
max_resolution=contour_resolution,
threshold=threshold,
)
def effective_cutout_edge_length(
context,
source_object,

View File

@@ -2450,7 +2450,7 @@ def refine_quality(mesh, opts, n_input_verts):
tmr = tri_metric(mesh, bad_idx, min_ang_val, global_max_a, use_region_area, cos_min_ang)
if tmr[0] <= 0:
continue
ang_viol, area_viol, area, effective_max_area = tmr[1], tmr[2], tmr[3], tmr[4]
ang_viol, area_viol, area, _ = tmr[1], tmr[2], tmr[3], tmr[4]
tmr_la2, tmr_lb2, tmr_lc2 = tmr[5], tmr[6], tmr[7]
a = mesh.vertices[bt.v[0]]

View File

@@ -98,23 +98,6 @@ def test_cutout_crop_bounds_map_to_the_source_plane():
np.testing.assert_allclose(bounds, (-0.5, 0.5, -0.25, 0.25))
def test_depth_surface_cutout_uses_default_alpha_boundary():
alpha = np.zeros((12, 16), dtype=np.float32)
alpha[2:10, 2:14] = 0.89
alpha[4:8, 5:11] = 0.91
selection_contours = cutout_geometry.extract_cutout_contours(
alpha,
64,
)
contour = np.asarray(selection_contours.components[0][0])
assert selection_contours.image_size == (16, 12)
np.testing.assert_allclose(contour.min(axis=0), (5.0, 4.0))
np.testing.assert_allclose(contour.max(axis=0), (11.0, 8.0))
def test_cutout_reference_mask_resamples_content_to_depth_resolution():
content = np.zeros((4, 4), dtype=np.float32)
content[:, 2:] = 1.0
@@ -149,21 +132,6 @@ def test_blender_cutout_alpha_pipeline_does_not_import_pillow():
assert selection_contours.components
assert np.any((antialiased > 0.0) & (antialiased < 1.0))
def test_blender_cutout_contour_resolution_tracks_sampling_resolution():
expected = SelectionContours(
components=(((((0, 0), (1, 0), (0, 1))),),),
image_size=(1, 1),
)
values = SimpleNamespace(shape=(1024, 1024))
with patch.object(cutout_geometry, "extract_selection_contours", return_value=expected) as trace:
assert cutout_geometry.extract_cutout_contours(values, 32) == expected
assert cutout_geometry.extract_cutout_contours(values, 256) == expected
assert trace.call_args_list[0].kwargs["max_resolution"] == 512
assert trace.call_args_list[1].kwargs["max_resolution"] == 1024
def test_cutout_domain_adapts_boundary_spacing_for_a_small_valid_selection():
components = ((((2046, 2046), (2050, 2046), (2050, 2050), (2046, 2050)),),)

View File

@@ -1,9 +1,8 @@
from types import SimpleNamespace
from unittest.mock import patch
import bpy
import numpy as np
import pytest
from mathutils import Matrix, Vector
from mathutils import Vector
from anyimage.common import depth as depth_data

View File

@@ -287,8 +287,6 @@ class FrameProjectionTest(unittest.TestCase):
def test_frame_keeps_positive_active_origin_as_result_depth_location(self):
import numpy as np
marker = SimpleNamespace(copy=lambda: "active-origin")
source = {
"object": SimpleNamespace(

View File

@@ -1,17 +1,11 @@
import bpy
import pytest
from tests.support.paths import PROJECT_ROOT
from tools.nodes.groups.image_layer import build_image_layer_group
@pytest.fixture
def image_layer(request):
def image_layer():
bpy.ops.wm.read_factory_settings(use_empty=True)
if getattr(request, "param", "definition") == "asset":
with bpy.data.libraries.load(str(PROJECT_ROOT / "src/anyimage/assets/O_AnyImage.blend")) as (_, target):
target.node_groups = ["O Image Layer"]
group = target.node_groups[0]
else:
group = build_image_layer_group()
group = build_image_layer_group()
yield group
bpy.ops.wm.read_factory_settings(use_empty=True)

View File

@@ -5,16 +5,31 @@ import numpy as np
import pytest
from tools.nodes.build import build_node_groups
from tools.nodes.check import ASSET_PATH, GEOMETRY_NAMES, SHADER_NAMES
from tools.nodes.arrangement.arrange import arrange_nodes
from tools.nodes.arrangement.dimensions import estimate_node_dimensions
from tools.nodes.check import ASSET_PATH, SHADER_NAMES
from tools.nodes.groups.image_depth_cutout import build_image_depth_cutout_group
from tools.nodes.groups.image_depth_layer import build_image_depth_layer_group
from tools.nodes.groups.image_layer import build_image_layer_group
from tools.nodes.groups.shadeless import build_shadeless_group
@pytest.fixture(params=sorted(GEOMETRY_NAMES | SHADER_NAMES))
BUILDERS = (
("O Image Depth Cutout", build_image_depth_cutout_group),
("O Image Layer", build_image_layer_group),
)
SHADER_BUILDERS = {
"O Image Layer": build_image_layer_group,
"O Image Depth Layer": build_image_depth_layer_group,
"O Shadeless": build_shadeless_group,
}
@pytest.fixture(params=[name for name, _ in BUILDERS])
def node_group(request):
bpy.ops.wm.read_factory_settings(use_empty=True)
return next(group for group in build_node_groups() if group.name == request.param)
return next(builder() for name, builder in BUILDERS if name == request.param)
def _source(socket):
@@ -148,16 +163,12 @@ def test_arrangement_preserves_program_and_evaluation(node_group):
assert all(not (box[0] < other[2] and box[2] > other[0]
and box[1] < other[3] and box[3] > other[1]) for other in boxes), node.name
boxes.append(box)
input_count = sum(node.bl_idname == "NodeGroupInput" for node in display_nodes)
arrange_nodes(node_group)
assert _program(node_group) == program
assert sum(node.bl_idname == "NodeGroupInput" for node in node_group.nodes) == input_count
@pytest.mark.parametrize("name", sorted(SHADER_NAMES))
def test_shader_asset_preserves_definition(name):
bpy.ops.wm.read_factory_settings(use_empty=True)
definition = next(group for group in build_node_groups() if group.name == name)
definition = SHADER_BUILDERS[name]()
expected = _program(definition)
with bpy.data.libraries.load(str(ASSET_PATH)) as (_, target):
target.node_groups = [name]

View File

@@ -177,9 +177,8 @@ def test_cutout_boundary_smooth_defaults_to_four():
assert (socket.min_value, socket.max_value) == (0, 16)
@pytest.mark.parametrize("triangles", [False, True])
def test_smoothing_reduces_stairs_without_leaving_two_ring_band(triangles):
obj, set_value = diagonal_surface(triangles)
def test_smoothing_reduces_stairs_without_leaving_two_ring_band():
obj, set_value = diagonal_surface(triangles=True)
original, faces = evaluated(obj)
boundary = boundary_neighbors(faces)
outer = np.isclose(original[:, 0], 0) | np.isclose(original[:, 0], 2)
@@ -223,8 +222,8 @@ def test_smoothing_without_split_moves_outline_only_within_boundary_band():
np.testing.assert_array_equal(evaluated(obj)[0], original)
@pytest.mark.parametrize("mode", [0, 1])
def test_smoothed_cut_shells_stay_closed_and_separate(mode):
def test_smoothed_cut_shells_stay_closed_and_separate():
mode = 1
from anyimage.common.object import modifier_input_identifier, set_modifier_input
obj, set_value = diagonal_surface(triangles=True)

View File

@@ -62,8 +62,8 @@ def test_cleaned_sawtooth_smoothing_preserves_face_orientation(iterations):
assert np.all(np.sum(normals(original)*normals(points), axis=1) > 0)
@pytest.mark.parametrize("resolution,aspect", [(12, .5), (24, 2)])
def test_split_profile_tapers_within_boundary_band(resolution, aspect):
def test_split_profile_tapers_within_boundary_band():
resolution, aspect = 12, .5
obj, set_value = surface(step=6, resolution=resolution)
for vertex in obj.data.vertices:
vertex.co.x *= aspect
@@ -96,8 +96,7 @@ def test_split_profile_tapers_within_boundary_band(resolution, aspect):
@pytest.mark.parametrize("mode", [0, 1])
@pytest.mark.parametrize("reference", [0, 2])
def test_normal_blend_weights_balloon_and_keeps_full_shell_smoothing(mode, reference):
def test_normal_blend_weights_balloon_and_keeps_full_shell_smoothing(mode):
obj, set_value = surface(resolution=2)
group = obj.modifiers[0].node_group
# The production interface renames the Shell thickness socket after building.
@@ -121,6 +120,7 @@ def test_normal_blend_weights_balloon_and_keeps_full_shell_smoothing(mode, refer
group.links.new(store.outputs[0], next(n for n in group.nodes if n.type == "GROUP_OUTPUT").inputs["Geometry"])
modifier = obj.modifiers[0]
identifier = socket.identifier if mode else modifier_input_identifier(group, "Thickness", subtype="NONE")
reference = 2
set_value("Reference Depth", reference)
results = []
for amount in (0, .001, .01, 1):

View File

@@ -8,7 +8,6 @@ import pytest
from scipy.spatial import cKDTree
from anyimage.common.object import modifier_input_identifier, set_modifier_input
from anyimage.operators.cutout_tool.object import load_cutout_node_group
from tests.support.depth_surface import assert_closed, evaluated
from tools.nodes.common.normal_map import AXIS_ATTRIBUTE, FACE_ATTRIBUTE, ROTATION_ATTRIBUTE
from tools.nodes.groups.image_depth_cutout import build_image_depth_cutout_group
@@ -28,8 +27,8 @@ def _set_value(obj, modifier, group):
return setter
@pytest.fixture(params=["definition", "asset"])
def symmetry(request):
@pytest.fixture
def symmetry():
bpy.ops.wm.read_factory_settings(use_empty=True)
count = 5
z, x = np.meshgrid(
@@ -70,16 +69,8 @@ def symmetry(request):
obj = bpy.data.objects.new("Symmetry", mesh)
bpy.context.collection.objects.link(obj)
cutout_group = (
build_image_depth_cutout_group()
if request.param == "definition"
else load_cutout_node_group("DEPTH_SOLID")
)
mirror_group = (
build_image_cutout_symmetry_group()
if request.param == "definition"
else load_cutout_node_group("DEPTH_SYMMETRY")
)
cutout_group = build_image_depth_cutout_group()
mirror_group = build_image_cutout_symmetry_group()
cutout = obj.modifiers.new(cutout_group.name, "NODES")
cutout.node_group = cutout_group
mirror = obj.modifiers.new(mirror_group.name, "NODES")
@@ -240,59 +231,6 @@ def test_fill_smooth_relaxes_the_junction_band(symmetry):
assert cKDTree(smooth_points).query(smooth_points * (-1, 1, 1))[0].max() < 1e-5
def test_fill_smooth_band_reaches_beyond_two_rings():
"""Smooth 平滑的是衔接带,覆盖范围比原先的两圈更宽。"""
from tools.nodes.common.depth_surface import _math
from tools.nodes.groups.image_cutout_symmetry import SMOOTH_RINGS, _falloff
bpy.ops.wm.read_factory_settings(use_empty=True)
size, rows = SMOOTH_RINGS + 4, 3
points = [(x, 0.0, y) for y in range(rows) for x in range(size)]
faces = [
(
y * size + x,
y * size + x + 1,
(y + 1) * size + x + 1,
(y + 1) * size + x,
)
for y in range(rows - 1)
for x in range(size - 1)
]
mesh = bpy.data.meshes.new("Fill band")
mesh.from_pydata(points, [], faces)
obj = bpy.data.objects.new("Fill band", mesh)
bpy.context.collection.objects.link(obj)
group = bpy.data.node_groups.new("Fill band", "GeometryNodeTree")
for direction in ("INPUT", "OUTPUT"):
group.interface.new_socket(
name="Geometry", in_out=direction, socket_type="NodeSocketGeometry"
)
source = group.nodes.new("NodeGroupInput")
position = group.nodes.new("GeometryNodeInputPosition")
axes = group.nodes.new("ShaderNodeSeparateXYZ")
group.links.new(position.outputs[0], axes.inputs[0])
outline = _math(group, "COMPARE", axes.outputs["X"], 0)
store = group.nodes.new("GeometryNodeStoreNamedAttribute")
store.data_type, store.domain = "FLOAT", "POINT"
store.inputs["Name"].default_value = "fill_band"
group.links.new(source.outputs["Geometry"], store.inputs["Geometry"])
group.links.new(_falloff(group, outline, SMOOTH_RINGS), store.inputs["Value"])
group.links.new(
store.outputs["Geometry"], group.nodes.new("NodeGroupOutput").inputs["Geometry"]
)
obj.modifiers.new("Fill band", "NODES").node_group = group
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
data = result.to_mesh()
try:
band = np.array(
[item.value for item in data.attributes["fill_band"].data]
).reshape(rows, size)
finally:
result.to_mesh_clear()
assert (band[:, 3] > 0).all()
assert (band[:, SMOOTH_RINGS + 1] == 0).all()
def test_object_normal_attributes_follow_final_orientation(symmetry):
obj, _cutout, _mirror, cutout_setter, _mirror_setter = symmetry
cutout_setter("Mode", 1)

View File

@@ -6,17 +6,15 @@ import numpy as np
import pytest
from tools.nodes.groups.image_cutout import build_image_cutout_group
from tests.support.nodes import CUTOUT_BUILDERS
from anyimage.operators.cutout_tool.object import load_cutout_node_group
from anyimage.common.object import modifier_input_identifier, set_modifier_input
@pytest.mark.parametrize("source", ("definition", "asset"))
@pytest.mark.parametrize("shape", ("SOLID", "DEPTH_SOLID"))
def test_depth_axis_is_removed_from_cutout_options(shape, source):
def test_depth_axis_is_removed_from_cutout_options(shape):
bpy.ops.wm.read_factory_settings(use_empty=True)
group = CUTOUT_BUILDERS[shape]() if source == "definition" else load_cutout_node_group(shape)
group = CUTOUT_BUILDERS[shape]()
inputs = {item.name: item for item in group.interface.items_tree
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
assert "Depth Axis" not in inputs

View File

@@ -1,10 +1,9 @@
"""Contracts for smooth depth volume, editable presets and normal masks."""
from collections import Counter
import bpy
import numpy as np
import pytest
from tools.nodes.groups.image_depth_cutout import build_image_depth_cutout_group, build_volume_fields
from tools.nodes.groups.image_depth_cutout import build_volume_fields
from anyimage.common.object import modifier_input_identifier, set_modifier_input
@@ -104,7 +103,6 @@ def set_test_structure(monkeypatch, value):
def test_shell_thickness_extends_behind_the_tilted_front():
from scipy.spatial import cKDTree
from tests.support.depth_surface import surface, evaluated, assert_closed
obj, set_value = surface(step=0, resolution=4)
image = bpy.data.images["Camera"]
@@ -210,15 +208,6 @@ def test_depth_cutout_normal_reduction_uses_point_domain():
evaluated.to_mesh_clear()
def test_depth_cutout_reuses_boundary_and_slow_field_nodes():
bpy.ops.wm.read_factory_settings(use_empty=True)
group = build_image_depth_cutout_group()
counts = Counter(node.bl_idname for node in group.nodes)
assert counts["GeometryNodeInputMeshEdgeNeighbors"] <= 15
# 内部平滑、边界条带与 pin sharp 的法线相干度各一个 Blur。
assert counts["GeometryNodeBlurAttribute"] <= 13
def test_center_derives_from_shared_slow_fields():
bpy.ops.wm.read_factory_settings(use_empty=True)
points = [(x, y) for y in range(3) for x in range(3)]
@@ -264,7 +253,7 @@ def test_zero_thickness_short_circuits_shell_without_warnings():
assert len(faces) == len(obj.data.polygons)
modifier = obj.modifiers[0]
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
mesh = result.to_mesh()
result.to_mesh()
try:
assert not list(modifier.node_warnings), [
warning.message for warning in modifier.node_warnings
@@ -274,8 +263,7 @@ def test_zero_thickness_short_circuits_shell_without_warnings():
@pytest.mark.parametrize("thickness,inflation", [
(0.0, 1.0), (0.02, 0.0), (0.02, 0.0001),
(0.02, 0.5), (0.1, 0.5), (0.1, 1.0),
(0.0, 1.0), (0.02, 0.5), (0.1, 1.0),
])
def test_shell_ignores_front_inflation(thickness, inflation):
from tests.support.depth_surface import surface, evaluated, assert_closed

View File

@@ -81,7 +81,7 @@ def test_relief_depth_scale_sets_normal_reduction(relief, depth_scale):
np.testing.assert_allclose(values, 1.0)
@pytest.mark.parametrize("thickness", [0.000001, 0.2, 2.0])
@pytest.mark.parametrize("thickness", [0.2, 2.0])
def test_shell_keeps_front_and_closes_rectangular_boundary(surface, thickness):
obj, set_value, _, _, _ = surface
set_value("Depth Scale", 1.0)
@@ -187,13 +187,10 @@ def test_relief_reads_z_and_keeps_the_base(relief, thickness):
assert obj.modifiers[0][inputs["Thickness"].identifier] == pytest.approx(thickness)
@pytest.mark.parametrize("reference", [0.5, 2.0])
def test_zero_thickness_relief_keeps_far_depth_at_object_space_zero(relief, reference):
def test_zero_thickness_relief_keeps_far_depth_at_object_space_zero(relief):
obj, set_value, _, _, _ = relief
base = evaluated(obj)
texel_x = np.clip((base[0][:, 0] + 2) / 4 * 16, 0.5, 15.5)
depth = (2 + texel_x / 16) * 0.5
set_value("Reference Depth", reference)
set_value("Reference Depth", 2.0)
set_value("Depth Scale", 1.5)
result = evaluated(obj)
assert np.isfinite(result[0]).all()
@@ -202,15 +199,14 @@ def test_zero_thickness_relief_keeps_far_depth_at_object_space_zero(relief, refe
np.testing.assert_array_equal(result[2], base[2])
@pytest.mark.parametrize("reference", [0.0, 0.9])
def test_relief_far_depth_stops_at_thickness_height(relief, reference):
def test_relief_far_depth_stops_at_thickness_height(relief):
obj, set_value, _, _, image = relief
pixels = np.ones((8, 16, 4), dtype=np.float32)
pixels[..., 2] = 2.0
image.pixels.foreach_set(pixels.ravel())
image.update()
set_value("Uniform Scale", 0.5)
set_value("Reference Depth", reference)
set_value("Reference Depth", 0.9)
set_value("Depth Scale", 2.0)
set_value("Thickness", 0.4)
points = evaluated(obj)[0]
@@ -236,11 +232,10 @@ def test_relief_offset_corrects_reference_before_direction_and_scale(relief):
assert not np.allclose(raised[:, 1], lowered[:, 1], atol=1e-6)
@pytest.mark.parametrize("offset", [-1.0, 1.0])
def test_zero_depth_scale_keeps_relief_at_depth_zero(relief, offset):
def test_zero_depth_scale_keeps_relief_at_depth_zero(relief):
obj, set_value, _, _, _ = relief
set_value("Thickness", 0.4)
set_value("Depth Offset", offset)
set_value("Depth Offset", 1.0)
set_value("Depth Scale", 0.0)
points = evaluated(obj)[0]
assert np.isfinite(points).all()
@@ -299,9 +294,9 @@ def test_curved_shell_smooths_normals_without_shrinking_thickness(surface):
@pytest.mark.parametrize("thickness", [0.0, 0.4])
@pytest.mark.parametrize("direction", [(0.2, 1, -0.1), (-0.2, 1, 0.1), (0.4, 1, -0.2), (0, 0, 0), (1, 0, 0)])
def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direction):
def test_relief_direction_adjusts_depth_and_keeps_base(relief):
thickness = 0.4
direction = (0.2, 1, -0.1)
obj, set_value, _, _, image = relief
pixels = np.ones((8, 16, 4), dtype=np.float32)
pixels[..., 2] = 2
@@ -326,8 +321,7 @@ def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direct
@pytest.mark.parametrize("pattern,threshold", [
("valid", 1.0), ("invalid", 0.0), ("invalid", 0.9),
("partial", 0.4), ("partial", 0.9), ("half", 0.9), ("stripe", 0.9),
("half", 0.9), ("stripe", 0.9),
])
def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, threshold):
obj, set_value, _, inputs, _ = surface
@@ -351,7 +345,7 @@ def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, thresho
def sampled(x):
return np.interp((x + 2) / 4 * 16 - 0.5, np.arange(16), alpha)
for subdivisions in (0, 2, 5):
for subdivisions in (0, 5):
set_value("Subdivide", subdivisions)
set_value("Depth Mask", False)
base = evaluated(obj)

View File

@@ -50,7 +50,7 @@ def cleaned_faces(vertices, faces, *, triangles=False):
# The center becomes isolated only after its three arms disappear.
([(0, 0), (1, 0), (2, 0), (1, 1)], {1}),
])
@pytest.mark.parametrize("offset, reverse", [(0, False), (1, False), (2, True), (3, True)])
@pytest.mark.parametrize("offset, reverse", [(0, False), (2, True)])
def test_quad_strips_and_corners_are_classified_once(cells, expected, offset, reverse):
vertices, indices, faces = [], {}, []
for x, y in cells:
@@ -125,9 +125,8 @@ def test_strips_are_removed_before_projection_and_closed_thickness(kind):
assert len(evaluated(obj)[1]) == (6 if kind == "cutout" else 4)
@pytest.mark.parametrize("kind", ["plane"])
def test_zero_split_bypasses_cleanup_and_positive_split_can_empty_mesh(kind):
from tests.support.depth_surface import surface, evaluated
def test_zero_split_bypasses_cleanup_and_positive_split_can_empty_mesh():
from tests.support.depth_surface import evaluated
from tests.support.planes import create_surface
obj, set_value, _, _, _ = create_surface("DEPTH")

View File

@@ -11,15 +11,14 @@ from tests.support.depth_surface import assert_closed, component_count, evaluate
@pytest.mark.parametrize("mode", [0, 1])
@pytest.mark.parametrize("split", [0.0, 0.5])
def test_shell_walls_directly_connect_caps(mode, split):
def test_shell_walls_directly_connect_caps(mode):
from anyimage.common.object import modifier_input_identifier, set_modifier_input
obj, set_value = surface(resolution=4)
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
"value", np.full(len(obj.data.vertices), 0.3)
)
set_value("Depth Split", split)
set_value("Depth Split", 0.5)
front, faces = evaluated(obj)
edges = Counter(tuple(sorted((a, b))) for face in faces for a, b in zip(face, (*face[1:], face[0])))
boundary_count = sum(count == 1 for count in edges.values())
@@ -47,10 +46,9 @@ def test_shell_walls_directly_connect_caps(mode, split):
@pytest.mark.parametrize("thickness", [0.1, 1.4])
@pytest.mark.parametrize("split", [0.0, 0.5])
def test_surface_caps_and_segmented_walls_are_connected(thickness, split):
def test_surface_caps_and_segmented_walls_are_connected(thickness):
obj, set_value = surface()
set_value("Depth Split", split)
set_value("Depth Split", 0.5)
set_value("Thickness", thickness)
coords, faces = evaluated(obj)
assert np.isfinite(coords).all()
@@ -231,15 +229,14 @@ def test_source_surface_sampling_preserves_depth_on_side_walls():
@pytest.mark.parametrize("thickness", [0.0, 0.2])
@pytest.mark.parametrize("split", [0.0, 0.5])
def test_temporary_fields_are_removed_and_user_attributes_survive(thickness, split):
def test_temporary_fields_are_removed_and_user_attributes_survive(thickness):
obj, set_value = surface()
obj.data.attributes.new("o_depth_probe", "FLOAT", "POINT")
attribute = obj.data.attributes.new("o_user_weight", "FLOAT", "POINT")
obj.data.attributes.new(".o_anyimage_user_weight", "FLOAT", "POINT")
for value in attribute.data:
value.value = 0.75
set_value("Depth Split", split)
set_value("Depth Split", 0.5)
set_value("Thickness", thickness)
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
mesh = result.to_mesh()

View File

@@ -54,10 +54,10 @@ def free_edges(faces):
return sum(count == 1 for count in edges.values())
@pytest.mark.parametrize("dome", [1.0, 50.0])
@pytest.mark.parametrize("subdivide", [0, 4])
def test_quad_sphere_topology_and_radial_endpoints(subdivide, dome):
def test_quad_sphere_topology_and_radial_endpoints(subdivide):
obj, set_value = panorama(subdivide=subdivide)
dome = 50.0
set_value("Dome Radius", dome)
inputs = [s for s in obj.modifiers[0].node_group.interface.items_tree if s.item_type == "SOCKET" and s.in_out == "INPUT"]
assert [s.name for s in inputs if s.socket_type == "NodeSocketImage"] == ["Depth Image"]
@@ -133,12 +133,11 @@ def test_masked_area_rests_on_the_dome_sphere():
assert len(faces) == 768
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 3, atol=1e-3)
set_value("Depth Mask", False)
for dome in (10.0, 50.0):
set_value("Dome Radius", dome)
radii = np.linalg.norm(evaluated(obj)[0], axis=1)
on_dome = np.isclose(radii, dome, atol=1e-3)
assert on_dome.any() and np.isclose(radii, 3, atol=1e-3).any()
assert np.all(on_dome | np.isclose(radii, 3, atol=1e-3))
set_value("Dome Radius", 50.0)
radii = np.linalg.norm(evaluated(obj)[0], axis=1)
on_dome = np.isclose(radii, 50.0, atol=1e-3)
assert on_dome.any() and np.isclose(radii, 3, atol=1e-3).any()
assert np.all(on_dome | np.isclose(radii, 3, atol=1e-3))
set_value("Dome Radius", 50.0)
set_value("Depth Scale", 0)
np.testing.assert_allclose(np.linalg.norm(evaluated(obj)[0], axis=1), 50, atol=1e-3)
@@ -284,7 +283,7 @@ def test_split_strength_and_distance_scale_invariance():
depth = np.ones((64, 128))
depth[:, 64:] = 7
signatures = []
for scale in (0.01, 1, 100):
for scale in (0.01, 100):
obj, set_value = panorama(depth * scale)
counts = []
for split in (0, 0.1, 0.5, 1):
@@ -296,7 +295,7 @@ def test_split_strength_and_distance_scale_invariance():
assert counts[0] == 0 and counts[-1] > 0
assert counts == sorted(counts)
signatures.append(counts)
assert signatures[0] == signatures[1] == signatures[2]
assert signatures[0] == signatures[1]
def test_continuous_spherical_slope_does_not_split_at_seam_or_poles():

View File

@@ -6,7 +6,6 @@ from tests.support.materials import image_layer
from tools.nodes.groups.image_depth_layer import build_image_depth_layer_group
@pytest.mark.parametrize("image_layer", ["definition", "asset"], indirect=True)
def test_alpha_curve_preserves_endpoints_and_suppresses_soft_edges(image_layer):
inputs = {
item.name: item for item in image_layer.interface.items_tree
@@ -16,7 +15,6 @@ def test_alpha_curve_preserves_endpoints_and_suppresses_soft_edges(image_layer):
"Color", "Alpha", "Normal", "Normal Scale", "Bump Scale",
"Alpha Fix", "Object Space",
]
strength = inputs["Alpha Fix"]
output = next(node for node in image_layer.nodes if node.type == "GROUP_OUTPUT")
curve = output.inputs["Alpha"].links[0].from_node
assert curve.type == "CURVE_FLOAT"

View File

@@ -1,5 +1,4 @@
import bpy
import pytest
from anyimage.common.object import (
modifier_input_identifier,
@@ -18,15 +17,14 @@ def evaluated_mesh(obj):
return evaluated, evaluated.to_mesh()
@pytest.mark.parametrize("source_count", [0, 4, 9])
def test_material_inheritance_removes_only_source_vertices(source_count):
def test_material_inheritance_removes_only_source_vertices():
from tools.nodes.common.material_slots import inherit_material_slots
from tools.nodes.arrangement.arrange import arrange_nodes
bpy.ops.wm.read_factory_settings(use_empty=True)
source_count = 4
mesh = bpy.data.meshes.new("Source")
mesh.from_pydata([(20 + i % 3, i // 3, 0) for i in range(source_count)], [],
[(0, 1, 3, 2)] if source_count else [])
[(0, 1, 3, 2)])
materials = [bpy.data.materials.new(name) for name in ("First", "Second")]
for material in materials:
mesh.materials.append(material)
@@ -42,20 +40,16 @@ def test_material_inheritance_removes_only_source_vertices(source_count):
geometry = inherit_material_slots(group, grid.outputs["Mesh"], source.outputs["Geometry"])
group.links.new(geometry, group.nodes.new("NodeGroupOutput").inputs["Geometry"])
obj.modifiers.new("Material Inheritance", "NODES").node_group = group
for arranged in (False, True):
if arranged:
arrange_nodes(group)
obj.update_tag(refresh={"DATA"})
ev, result = evaluated_mesh(obj)
try:
assert len(result.vertices) == 6 and len(result.polygons) == 2
assert all(abs(v.co.x) <= 0.5 for v in result.vertices)
if source_count:
assert [material.original for material in result.materials if material] == materials
assert result.materials[0].original == materials[0]
assert all(face.material_index == 0 for face in result.polygons)
finally:
ev.to_mesh_clear()
obj.update_tag(refresh={"DATA"})
ev, result = evaluated_mesh(obj)
try:
assert len(result.vertices) == 6 and len(result.polygons) == 2
assert all(abs(v.co.x) <= 0.5 for v in result.vertices)
assert [material.original for material in result.materials if material] == materials
assert result.materials[0].original == materials[0]
assert all(face.material_index == 0 for face in result.polygons)
finally:
ev.to_mesh_clear()
def test_saved_image_plane_evaluation():

View File

@@ -1,6 +1,5 @@
"""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
@@ -17,25 +16,12 @@ def test_saved_parameter_descriptions_match_definitions():
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 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 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"},
}
@@ -52,113 +38,7 @@ def test_saved_parameter_descriptions_match_definitions():
]
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
]
result[group.name] = [(item.identifier, item.name, item.description) for item in sockets]
assert result.keys() == parameters.keys()
return result
@@ -170,17 +50,11 @@ def test_saved_parameter_descriptions_match_definitions():
assert descriptions(target.node_groups) == expected
@pytest.mark.parametrize("source", ["definition", "asset"])
def test_geometry_nodes_contribute_to_output(source):
def test_geometry_nodes_contribute_to_output():
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
groups = build_node_groups()
for group in groups:
if group.bl_idname != "GeometryNodeTree":
continue
@@ -199,17 +73,11 @@ def test_geometry_nodes_contribute_to_output(source):
assert not unused, (group.name, unused)
@pytest.mark.parametrize("source", ["definition", "asset"])
def test_geometry_attribute_contract_includes_nested_groups(source):
def test_geometry_attribute_contract_includes_nested_groups():
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)
pending = build_node_groups()
seen = set()
allowed = {
"UVMap": ("FLOAT2", "CORNER"),

View File

@@ -7,7 +7,6 @@ from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
from tests.support.materials import image_layer
@pytest.mark.parametrize('image_layer', ['definition', 'asset'], indirect=True)
@pytest.mark.parametrize('engine', ['CYCLES', 'BLENDER_EEVEE_NEXT'])
@pytest.mark.parametrize('domain', ['POINT', 'FACE'])
def test_normal_scale_preserves_missing_and_zero_weights(image_layer, engine, domain, tmp_path):

View File

@@ -12,7 +12,6 @@ from tools.nodes.common.normal_map import ROTATION_ATTRIBUTE, FACE_ATTRIBUTE, AX
SYMMETRY_Y_TO_X = Matrix.Rotation(1.5707963267948966, 4, "Z").to_3x3()
@pytest.mark.parametrize('image_layer', ['definition', 'asset'], indirect=True)
@pytest.mark.parametrize('engine', ['CYCLES', 'BLENDER_EEVEE_NEXT'])
def test_object_normals_follow_rotation_mirror_axis_and_wall_mask(image_layer, engine, tmp_path):
scene = bpy.context.scene

View File

@@ -5,7 +5,7 @@ import bpy
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
from ..common.nodes import group_input as _group_input, interface_socket as _interface_socket
from ..common.nodes import store_float_attribute
from ..common.nodes import compare_node, prepare_node_group, float_input, vector_input
from ..common.nodes import compare_node, prepare_node_group, vector_input
from ..common.reference_plane import reference_plane_depth
from ..common.boundary_smoothing import boundary_influence