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:
@@ -238,10 +238,6 @@ def extract_selection_contours(selection_values, max_resolution=512, threshold=0
|
|||||||
image_size=source_size,
|
image_size=source_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
POINT_SPACING = 0.1
|
|
||||||
|
|
||||||
|
|
||||||
def contour_area(points):
|
def contour_area(points):
|
||||||
import numpy as np
|
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(
|
def effective_cutout_edge_length(
|
||||||
context,
|
context,
|
||||||
source_object,
|
source_object,
|
||||||
|
|||||||
@@ -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)
|
tmr = tri_metric(mesh, bad_idx, min_ang_val, global_max_a, use_region_area, cos_min_ang)
|
||||||
if tmr[0] <= 0:
|
if tmr[0] <= 0:
|
||||||
continue
|
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]
|
tmr_la2, tmr_lb2, tmr_lc2 = tmr[5], tmr[6], tmr[7]
|
||||||
|
|
||||||
a = mesh.vertices[bt.v[0]]
|
a = mesh.vertices[bt.v[0]]
|
||||||
|
|||||||
@@ -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))
|
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():
|
def test_cutout_reference_mask_resamples_content_to_depth_resolution():
|
||||||
content = np.zeros((4, 4), dtype=np.float32)
|
content = np.zeros((4, 4), dtype=np.float32)
|
||||||
content[:, 2:] = 1.0
|
content[:, 2:] = 1.0
|
||||||
@@ -149,21 +132,6 @@ def test_blender_cutout_alpha_pipeline_does_not_import_pillow():
|
|||||||
assert selection_contours.components
|
assert selection_contours.components
|
||||||
assert np.any((antialiased > 0.0) & (antialiased < 1.0))
|
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():
|
def test_cutout_domain_adapts_boundary_spacing_for_a_small_valid_selection():
|
||||||
components = ((((2046, 2046), (2050, 2046), (2050, 2050), (2046, 2050)),),)
|
components = ((((2046, 2046), (2050, 2046), (2050, 2050), (2046, 2050)),),)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from types import SimpleNamespace
|
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
from mathutils import Matrix, Vector
|
from mathutils import Vector
|
||||||
|
|
||||||
|
|
||||||
from anyimage.common import depth as depth_data
|
from anyimage.common import depth as depth_data
|
||||||
|
|||||||
@@ -287,8 +287,6 @@ class FrameProjectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def test_frame_keeps_positive_active_origin_as_result_depth_location(self):
|
def test_frame_keeps_positive_active_origin_as_result_depth_location(self):
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
marker = SimpleNamespace(copy=lambda: "active-origin")
|
marker = SimpleNamespace(copy=lambda: "active-origin")
|
||||||
source = {
|
source = {
|
||||||
"object": SimpleNamespace(
|
"object": SimpleNamespace(
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import pytest
|
import pytest
|
||||||
from tests.support.paths import PROJECT_ROOT
|
|
||||||
from tools.nodes.groups.image_layer import build_image_layer_group
|
from tools.nodes.groups.image_layer import build_image_layer_group
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def image_layer(request):
|
def image_layer():
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
if getattr(request, "param", "definition") == "asset":
|
group = build_image_layer_group()
|
||||||
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()
|
|
||||||
yield group
|
yield group
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
|
|||||||
@@ -5,16 +5,31 @@ import numpy as np
|
|||||||
import pytest
|
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.arrange import arrange_nodes
|
||||||
from tools.nodes.arrangement.dimensions import estimate_node_dimensions
|
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):
|
def node_group(request):
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
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):
|
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]
|
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
|
and box[1] < other[3] and box[3] > other[1]) for other in boxes), node.name
|
||||||
boxes.append(box)
|
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))
|
@pytest.mark.parametrize("name", sorted(SHADER_NAMES))
|
||||||
def test_shader_asset_preserves_definition(name):
|
def test_shader_asset_preserves_definition(name):
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
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)
|
expected = _program(definition)
|
||||||
with bpy.data.libraries.load(str(ASSET_PATH)) as (_, target):
|
with bpy.data.libraries.load(str(ASSET_PATH)) as (_, target):
|
||||||
target.node_groups = [name]
|
target.node_groups = [name]
|
||||||
|
|||||||
@@ -177,9 +177,8 @@ def test_cutout_boundary_smooth_defaults_to_four():
|
|||||||
assert (socket.min_value, socket.max_value) == (0, 16)
|
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():
|
||||||
def test_smoothing_reduces_stairs_without_leaving_two_ring_band(triangles):
|
obj, set_value = diagonal_surface(triangles=True)
|
||||||
obj, set_value = diagonal_surface(triangles)
|
|
||||||
original, faces = evaluated(obj)
|
original, faces = evaluated(obj)
|
||||||
boundary = boundary_neighbors(faces)
|
boundary = boundary_neighbors(faces)
|
||||||
outer = np.isclose(original[:, 0], 0) | np.isclose(original[:, 0], 2)
|
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)
|
np.testing.assert_array_equal(evaluated(obj)[0], original)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", [0, 1])
|
def test_smoothed_cut_shells_stay_closed_and_separate():
|
||||||
def test_smoothed_cut_shells_stay_closed_and_separate(mode):
|
mode = 1
|
||||||
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
||||||
|
|
||||||
obj, set_value = diagonal_surface(triangles=True)
|
obj, set_value = diagonal_surface(triangles=True)
|
||||||
|
|||||||
@@ -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)
|
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():
|
||||||
def test_split_profile_tapers_within_boundary_band(resolution, aspect):
|
resolution, aspect = 12, .5
|
||||||
obj, set_value = surface(step=6, resolution=resolution)
|
obj, set_value = surface(step=6, resolution=resolution)
|
||||||
for vertex in obj.data.vertices:
|
for vertex in obj.data.vertices:
|
||||||
vertex.co.x *= aspect
|
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("mode", [0, 1])
|
||||||
@pytest.mark.parametrize("reference", [0, 2])
|
def test_normal_blend_weights_balloon_and_keeps_full_shell_smoothing(mode):
|
||||||
def test_normal_blend_weights_balloon_and_keeps_full_shell_smoothing(mode, reference):
|
|
||||||
obj, set_value = surface(resolution=2)
|
obj, set_value = surface(resolution=2)
|
||||||
group = obj.modifiers[0].node_group
|
group = obj.modifiers[0].node_group
|
||||||
# The production interface renames the Shell thickness socket after building.
|
# 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"])
|
group.links.new(store.outputs[0], next(n for n in group.nodes if n.type == "GROUP_OUTPUT").inputs["Geometry"])
|
||||||
modifier = obj.modifiers[0]
|
modifier = obj.modifiers[0]
|
||||||
identifier = socket.identifier if mode else modifier_input_identifier(group, "Thickness", subtype="NONE")
|
identifier = socket.identifier if mode else modifier_input_identifier(group, "Thickness", subtype="NONE")
|
||||||
|
reference = 2
|
||||||
set_value("Reference Depth", reference)
|
set_value("Reference Depth", reference)
|
||||||
results = []
|
results = []
|
||||||
for amount in (0, .001, .01, 1):
|
for amount in (0, .001, .01, 1):
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import pytest
|
|||||||
from scipy.spatial import cKDTree
|
from scipy.spatial import cKDTree
|
||||||
|
|
||||||
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
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 tests.support.depth_surface import assert_closed, evaluated
|
||||||
from tools.nodes.common.normal_map import AXIS_ATTRIBUTE, FACE_ATTRIBUTE, ROTATION_ATTRIBUTE
|
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
|
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
|
return setter
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=["definition", "asset"])
|
@pytest.fixture
|
||||||
def symmetry(request):
|
def symmetry():
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
count = 5
|
count = 5
|
||||||
z, x = np.meshgrid(
|
z, x = np.meshgrid(
|
||||||
@@ -70,16 +69,8 @@ def symmetry(request):
|
|||||||
obj = bpy.data.objects.new("Symmetry", mesh)
|
obj = bpy.data.objects.new("Symmetry", mesh)
|
||||||
bpy.context.collection.objects.link(obj)
|
bpy.context.collection.objects.link(obj)
|
||||||
|
|
||||||
cutout_group = (
|
cutout_group = build_image_depth_cutout_group()
|
||||||
build_image_depth_cutout_group()
|
mirror_group = build_image_cutout_symmetry_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 = obj.modifiers.new(cutout_group.name, "NODES")
|
cutout = obj.modifiers.new(cutout_group.name, "NODES")
|
||||||
cutout.node_group = cutout_group
|
cutout.node_group = cutout_group
|
||||||
mirror = obj.modifiers.new(mirror_group.name, "NODES")
|
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
|
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):
|
def test_object_normal_attributes_follow_final_orientation(symmetry):
|
||||||
obj, _cutout, _mirror, cutout_setter, _mirror_setter = symmetry
|
obj, _cutout, _mirror, cutout_setter, _mirror_setter = symmetry
|
||||||
cutout_setter("Mode", 1)
|
cutout_setter("Mode", 1)
|
||||||
|
|||||||
@@ -6,17 +6,15 @@ import numpy as np
|
|||||||
import pytest
|
import pytest
|
||||||
from tools.nodes.groups.image_cutout import build_image_cutout_group
|
from tools.nodes.groups.image_cutout import build_image_cutout_group
|
||||||
from tests.support.nodes import CUTOUT_BUILDERS
|
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
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source", ("definition", "asset"))
|
|
||||||
@pytest.mark.parametrize("shape", ("SOLID", "DEPTH_SOLID"))
|
@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)
|
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
|
inputs = {item.name: item for item in group.interface.items_tree
|
||||||
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
|
if item.item_type == "SOCKET" and item.in_out == "INPUT"}
|
||||||
assert "Depth Axis" not in inputs
|
assert "Depth Axis" not in inputs
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
"""Contracts for smooth depth volume, editable presets and normal masks."""
|
"""Contracts for smooth depth volume, editable presets and normal masks."""
|
||||||
from collections import Counter
|
|
||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
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
|
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():
|
def test_shell_thickness_extends_behind_the_tilted_front():
|
||||||
from scipy.spatial import cKDTree
|
|
||||||
from tests.support.depth_surface import surface, evaluated, assert_closed
|
from tests.support.depth_surface import surface, evaluated, assert_closed
|
||||||
obj, set_value = surface(step=0, resolution=4)
|
obj, set_value = surface(step=0, resolution=4)
|
||||||
image = bpy.data.images["Camera"]
|
image = bpy.data.images["Camera"]
|
||||||
@@ -210,15 +208,6 @@ def test_depth_cutout_normal_reduction_uses_point_domain():
|
|||||||
evaluated.to_mesh_clear()
|
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():
|
def test_center_derives_from_shared_slow_fields():
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
points = [(x, y) for y in range(3) for x in range(3)]
|
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)
|
assert len(faces) == len(obj.data.polygons)
|
||||||
modifier = obj.modifiers[0]
|
modifier = obj.modifiers[0]
|
||||||
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||||
mesh = result.to_mesh()
|
result.to_mesh()
|
||||||
try:
|
try:
|
||||||
assert not list(modifier.node_warnings), [
|
assert not list(modifier.node_warnings), [
|
||||||
warning.message for warning in 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", [
|
@pytest.mark.parametrize("thickness,inflation", [
|
||||||
(0.0, 1.0), (0.02, 0.0), (0.02, 0.0001),
|
(0.0, 1.0), (0.02, 0.5), (0.1, 1.0),
|
||||||
(0.02, 0.5), (0.1, 0.5), (0.1, 1.0),
|
|
||||||
])
|
])
|
||||||
def test_shell_ignores_front_inflation(thickness, inflation):
|
def test_shell_ignores_front_inflation(thickness, inflation):
|
||||||
from tests.support.depth_surface import surface, evaluated, assert_closed
|
from tests.support.depth_surface import surface, evaluated, assert_closed
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ def test_relief_depth_scale_sets_normal_reduction(relief, depth_scale):
|
|||||||
np.testing.assert_allclose(values, 1.0)
|
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):
|
def test_shell_keeps_front_and_closes_rectangular_boundary(surface, thickness):
|
||||||
obj, set_value, _, _, _ = surface
|
obj, set_value, _, _, _ = surface
|
||||||
set_value("Depth Scale", 1.0)
|
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)
|
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):
|
||||||
def test_zero_thickness_relief_keeps_far_depth_at_object_space_zero(relief, reference):
|
|
||||||
obj, set_value, _, _, _ = relief
|
obj, set_value, _, _, _ = relief
|
||||||
base = evaluated(obj)
|
base = evaluated(obj)
|
||||||
texel_x = np.clip((base[0][:, 0] + 2) / 4 * 16, 0.5, 15.5)
|
set_value("Reference Depth", 2.0)
|
||||||
depth = (2 + texel_x / 16) * 0.5
|
|
||||||
set_value("Reference Depth", reference)
|
|
||||||
set_value("Depth Scale", 1.5)
|
set_value("Depth Scale", 1.5)
|
||||||
result = evaluated(obj)
|
result = evaluated(obj)
|
||||||
assert np.isfinite(result[0]).all()
|
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])
|
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):
|
||||||
def test_relief_far_depth_stops_at_thickness_height(relief, reference):
|
|
||||||
obj, set_value, _, _, image = relief
|
obj, set_value, _, _, image = relief
|
||||||
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
||||||
pixels[..., 2] = 2.0
|
pixels[..., 2] = 2.0
|
||||||
image.pixels.foreach_set(pixels.ravel())
|
image.pixels.foreach_set(pixels.ravel())
|
||||||
image.update()
|
image.update()
|
||||||
set_value("Uniform Scale", 0.5)
|
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("Depth Scale", 2.0)
|
||||||
set_value("Thickness", 0.4)
|
set_value("Thickness", 0.4)
|
||||||
points = evaluated(obj)[0]
|
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)
|
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):
|
||||||
def test_zero_depth_scale_keeps_relief_at_depth_zero(relief, offset):
|
|
||||||
obj, set_value, _, _, _ = relief
|
obj, set_value, _, _, _ = relief
|
||||||
set_value("Thickness", 0.4)
|
set_value("Thickness", 0.4)
|
||||||
set_value("Depth Offset", offset)
|
set_value("Depth Offset", 1.0)
|
||||||
set_value("Depth Scale", 0.0)
|
set_value("Depth Scale", 0.0)
|
||||||
points = evaluated(obj)[0]
|
points = evaluated(obj)[0]
|
||||||
assert np.isfinite(points).all()
|
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])
|
def test_relief_direction_adjusts_depth_and_keeps_base(relief):
|
||||||
@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)])
|
thickness = 0.4
|
||||||
def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direction):
|
direction = (0.2, 1, -0.1)
|
||||||
obj, set_value, _, _, image = relief
|
obj, set_value, _, _, image = relief
|
||||||
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
||||||
pixels[..., 2] = 2
|
pixels[..., 2] = 2
|
||||||
@@ -326,8 +321,7 @@ def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direct
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("pattern,threshold", [
|
@pytest.mark.parametrize("pattern,threshold", [
|
||||||
("valid", 1.0), ("invalid", 0.0), ("invalid", 0.9),
|
("half", 0.9), ("stripe", 0.9),
|
||||||
("partial", 0.4), ("partial", 0.9), ("half", 0.9), ("stripe", 0.9),
|
|
||||||
])
|
])
|
||||||
def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, threshold):
|
def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, threshold):
|
||||||
obj, set_value, _, inputs, _ = surface
|
obj, set_value, _, inputs, _ = surface
|
||||||
@@ -351,7 +345,7 @@ def test_valid_only_keeps_faces_covering_valid_samples(surface, pattern, thresho
|
|||||||
def sampled(x):
|
def sampled(x):
|
||||||
return np.interp((x + 2) / 4 * 16 - 0.5, np.arange(16), alpha)
|
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("Subdivide", subdivisions)
|
||||||
set_value("Depth Mask", False)
|
set_value("Depth Mask", False)
|
||||||
base = evaluated(obj)
|
base = evaluated(obj)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def cleaned_faces(vertices, faces, *, triangles=False):
|
|||||||
# The center becomes isolated only after its three arms disappear.
|
# The center becomes isolated only after its three arms disappear.
|
||||||
([(0, 0), (1, 0), (2, 0), (1, 1)], {1}),
|
([(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):
|
def test_quad_strips_and_corners_are_classified_once(cells, expected, offset, reverse):
|
||||||
vertices, indices, faces = [], {}, []
|
vertices, indices, faces = [], {}, []
|
||||||
for x, y in cells:
|
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)
|
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():
|
||||||
def test_zero_split_bypasses_cleanup_and_positive_split_can_empty_mesh(kind):
|
from tests.support.depth_surface import evaluated
|
||||||
from tests.support.depth_surface import surface, evaluated
|
|
||||||
from tests.support.planes import create_surface
|
from tests.support.planes import create_surface
|
||||||
|
|
||||||
obj, set_value, _, _, _ = create_surface("DEPTH")
|
obj, set_value, _, _, _ = create_surface("DEPTH")
|
||||||
|
|||||||
@@ -11,15 +11,14 @@ from tests.support.depth_surface import assert_closed, component_count, evaluate
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", [0, 1])
|
@pytest.mark.parametrize("mode", [0, 1])
|
||||||
@pytest.mark.parametrize("split", [0.0, 0.5])
|
def test_shell_walls_directly_connect_caps(mode):
|
||||||
def test_shell_walls_directly_connect_caps(mode, split):
|
|
||||||
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
||||||
|
|
||||||
obj, set_value = surface(resolution=4)
|
obj, set_value = surface(resolution=4)
|
||||||
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
|
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
|
||||||
"value", np.full(len(obj.data.vertices), 0.3)
|
"value", np.full(len(obj.data.vertices), 0.3)
|
||||||
)
|
)
|
||||||
set_value("Depth Split", split)
|
set_value("Depth Split", 0.5)
|
||||||
front, faces = evaluated(obj)
|
front, faces = evaluated(obj)
|
||||||
edges = Counter(tuple(sorted((a, b))) for face in faces for a, b in zip(face, (*face[1:], face[0])))
|
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())
|
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("thickness", [0.1, 1.4])
|
||||||
@pytest.mark.parametrize("split", [0.0, 0.5])
|
def test_surface_caps_and_segmented_walls_are_connected(thickness):
|
||||||
def test_surface_caps_and_segmented_walls_are_connected(thickness, split):
|
|
||||||
obj, set_value = surface()
|
obj, set_value = surface()
|
||||||
set_value("Depth Split", split)
|
set_value("Depth Split", 0.5)
|
||||||
set_value("Thickness", thickness)
|
set_value("Thickness", thickness)
|
||||||
coords, faces = evaluated(obj)
|
coords, faces = evaluated(obj)
|
||||||
assert np.isfinite(coords).all()
|
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("thickness", [0.0, 0.2])
|
||||||
@pytest.mark.parametrize("split", [0.0, 0.5])
|
def test_temporary_fields_are_removed_and_user_attributes_survive(thickness):
|
||||||
def test_temporary_fields_are_removed_and_user_attributes_survive(thickness, split):
|
|
||||||
obj, set_value = surface()
|
obj, set_value = surface()
|
||||||
obj.data.attributes.new("o_depth_probe", "FLOAT", "POINT")
|
obj.data.attributes.new("o_depth_probe", "FLOAT", "POINT")
|
||||||
attribute = obj.data.attributes.new("o_user_weight", "FLOAT", "POINT")
|
attribute = obj.data.attributes.new("o_user_weight", "FLOAT", "POINT")
|
||||||
obj.data.attributes.new(".o_anyimage_user_weight", "FLOAT", "POINT")
|
obj.data.attributes.new(".o_anyimage_user_weight", "FLOAT", "POINT")
|
||||||
for value in attribute.data:
|
for value in attribute.data:
|
||||||
value.value = 0.75
|
value.value = 0.75
|
||||||
set_value("Depth Split", split)
|
set_value("Depth Split", 0.5)
|
||||||
set_value("Thickness", thickness)
|
set_value("Thickness", thickness)
|
||||||
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||||
mesh = result.to_mesh()
|
mesh = result.to_mesh()
|
||||||
|
|||||||
@@ -54,10 +54,10 @@ def free_edges(faces):
|
|||||||
return sum(count == 1 for count in edges.values())
|
return sum(count == 1 for count in edges.values())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("dome", [1.0, 50.0])
|
|
||||||
@pytest.mark.parametrize("subdivide", [0, 4])
|
@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)
|
obj, set_value = panorama(subdivide=subdivide)
|
||||||
|
dome = 50.0
|
||||||
set_value("Dome Radius", dome)
|
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"]
|
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"]
|
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
|
assert len(faces) == 768
|
||||||
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 3, atol=1e-3)
|
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 3, atol=1e-3)
|
||||||
set_value("Depth Mask", False)
|
set_value("Depth Mask", False)
|
||||||
for dome in (10.0, 50.0):
|
set_value("Dome Radius", 50.0)
|
||||||
set_value("Dome Radius", dome)
|
radii = np.linalg.norm(evaluated(obj)[0], axis=1)
|
||||||
radii = np.linalg.norm(evaluated(obj)[0], axis=1)
|
on_dome = np.isclose(radii, 50.0, atol=1e-3)
|
||||||
on_dome = np.isclose(radii, dome, atol=1e-3)
|
assert on_dome.any() and np.isclose(radii, 3, atol=1e-3).any()
|
||||||
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))
|
||||||
assert np.all(on_dome | np.isclose(radii, 3, atol=1e-3))
|
|
||||||
set_value("Dome Radius", 50.0)
|
set_value("Dome Radius", 50.0)
|
||||||
set_value("Depth Scale", 0)
|
set_value("Depth Scale", 0)
|
||||||
np.testing.assert_allclose(np.linalg.norm(evaluated(obj)[0], axis=1), 50, atol=1e-3)
|
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 = np.ones((64, 128))
|
||||||
depth[:, 64:] = 7
|
depth[:, 64:] = 7
|
||||||
signatures = []
|
signatures = []
|
||||||
for scale in (0.01, 1, 100):
|
for scale in (0.01, 100):
|
||||||
obj, set_value = panorama(depth * scale)
|
obj, set_value = panorama(depth * scale)
|
||||||
counts = []
|
counts = []
|
||||||
for split in (0, 0.1, 0.5, 1):
|
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[0] == 0 and counts[-1] > 0
|
||||||
assert counts == sorted(counts)
|
assert counts == sorted(counts)
|
||||||
signatures.append(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():
|
def test_continuous_spherical_slope_does_not_split_at_seam_or_poles():
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from tests.support.materials import image_layer
|
|||||||
from tools.nodes.groups.image_depth_layer import build_image_depth_layer_group
|
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):
|
def test_alpha_curve_preserves_endpoints_and_suppresses_soft_edges(image_layer):
|
||||||
inputs = {
|
inputs = {
|
||||||
item.name: item for item in image_layer.interface.items_tree
|
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",
|
"Color", "Alpha", "Normal", "Normal Scale", "Bump Scale",
|
||||||
"Alpha Fix", "Object Space",
|
"Alpha Fix", "Object Space",
|
||||||
]
|
]
|
||||||
strength = inputs["Alpha Fix"]
|
|
||||||
output = next(node for node in image_layer.nodes if node.type == "GROUP_OUTPUT")
|
output = next(node for node in image_layer.nodes if node.type == "GROUP_OUTPUT")
|
||||||
curve = output.inputs["Alpha"].links[0].from_node
|
curve = output.inputs["Alpha"].links[0].from_node
|
||||||
assert curve.type == "CURVE_FLOAT"
|
assert curve.type == "CURVE_FLOAT"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import pytest
|
|
||||||
|
|
||||||
from anyimage.common.object import (
|
from anyimage.common.object import (
|
||||||
modifier_input_identifier,
|
modifier_input_identifier,
|
||||||
@@ -18,15 +17,14 @@ def evaluated_mesh(obj):
|
|||||||
return evaluated, evaluated.to_mesh()
|
return evaluated, evaluated.to_mesh()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source_count", [0, 4, 9])
|
def test_material_inheritance_removes_only_source_vertices():
|
||||||
def test_material_inheritance_removes_only_source_vertices(source_count):
|
|
||||||
from tools.nodes.common.material_slots import inherit_material_slots
|
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)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
|
source_count = 4
|
||||||
mesh = bpy.data.meshes.new("Source")
|
mesh = bpy.data.meshes.new("Source")
|
||||||
mesh.from_pydata([(20 + i % 3, i // 3, 0) for i in range(source_count)], [],
|
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")]
|
materials = [bpy.data.materials.new(name) for name in ("First", "Second")]
|
||||||
for material in materials:
|
for material in materials:
|
||||||
mesh.materials.append(material)
|
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"])
|
geometry = inherit_material_slots(group, grid.outputs["Mesh"], source.outputs["Geometry"])
|
||||||
group.links.new(geometry, group.nodes.new("NodeGroupOutput").inputs["Geometry"])
|
group.links.new(geometry, group.nodes.new("NodeGroupOutput").inputs["Geometry"])
|
||||||
obj.modifiers.new("Material Inheritance", "NODES").node_group = group
|
obj.modifiers.new("Material Inheritance", "NODES").node_group = group
|
||||||
for arranged in (False, True):
|
obj.update_tag(refresh={"DATA"})
|
||||||
if arranged:
|
ev, result = evaluated_mesh(obj)
|
||||||
arrange_nodes(group)
|
try:
|
||||||
obj.update_tag(refresh={"DATA"})
|
assert len(result.vertices) == 6 and len(result.polygons) == 2
|
||||||
ev, result = evaluated_mesh(obj)
|
assert all(abs(v.co.x) <= 0.5 for v in result.vertices)
|
||||||
try:
|
assert [material.original for material in result.materials if material] == materials
|
||||||
assert len(result.vertices) == 6 and len(result.polygons) == 2
|
assert result.materials[0].original == materials[0]
|
||||||
assert all(abs(v.co.x) <= 0.5 for v in result.vertices)
|
assert all(face.material_index == 0 for face in result.polygons)
|
||||||
if source_count:
|
finally:
|
||||||
assert [material.original for material in result.materials if material] == materials
|
ev.to_mesh_clear()
|
||||||
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():
|
def test_saved_image_plane_evaluation():
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Check the saved library's public inventory and persistence."""
|
"""Check the saved library's public inventory and persistence."""
|
||||||
import bpy
|
import bpy
|
||||||
import pytest
|
|
||||||
from anyimage.common.node import node_asset_path
|
from anyimage.common.node import node_asset_path
|
||||||
from tools.nodes.check import GEOMETRY_NAMES
|
from tools.nodes.check import GEOMETRY_NAMES
|
||||||
|
|
||||||
@@ -17,25 +16,12 @@ def test_saved_parameter_descriptions_match_definitions():
|
|||||||
|
|
||||||
parameters = {
|
parameters = {
|
||||||
"O Image Plane": {"Subdivide"},
|
"O Image Plane": {"Subdivide"},
|
||||||
"O Image Depth Plane": {
|
"O Image Depth Plane": {"Subdivide", "Thickness", "Depth Split", "Depth Mask", "Mask Threshold", "Boundary Smooth"},
|
||||||
"Subdivide", "Thickness", "Depth Split",
|
"O Image Relief Plane": {"Subdivide", "Thickness", "Depth Direction", "Depth Offset"},
|
||||||
"Depth Mask", "Mask Threshold", "Boundary Smooth",
|
|
||||||
},
|
|
||||||
"O Image Relief Plane": {
|
|
||||||
"Subdivide", "Thickness", "Depth Direction", "Depth Offset",
|
|
||||||
},
|
|
||||||
"O Image Cutout": {"Thickness"},
|
"O Image Cutout": {"Thickness"},
|
||||||
"O Image Depth Cutout": {
|
"O Image Depth Cutout": {"Thickness", "Depth Split", "Boundary Smooth", "Normal Smooth", "Front Inflation"},
|
||||||
"Thickness", "Depth Split",
|
"O Image Cutout Symmetry": {"Direction", "Offset", "Scale", "Fill Sides", "Smooth"},
|
||||||
"Boundary Smooth", "Normal Smooth", "Front Inflation",
|
"O Image Depth Panorama": {"Subdivide", "Depth Scale", "Depth Split", "Dome Radius", "Depth Mask", "Mask Threshold", "Boundary Smooth"},
|
||||||
},
|
|
||||||
"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 Layer": {"Alpha Fix", "Normal Scale", "Bump Scale", "Object Space"},
|
||||||
"O Image Depth Layer": {"Bump Scale"},
|
"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 {item.name for item in sockets} == parameters[group.name]
|
||||||
assert all(item.description for item in sockets), group.name
|
assert all(item.description for item in sockets), group.name
|
||||||
input_names = {
|
result[group.name] = [(item.identifier, item.name, item.description) for item in sockets]
|
||||||
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()
|
assert result.keys() == parameters.keys()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -170,17 +50,11 @@ def test_saved_parameter_descriptions_match_definitions():
|
|||||||
assert descriptions(target.node_groups) == expected
|
assert descriptions(target.node_groups) == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source", ["definition", "asset"])
|
def test_geometry_nodes_contribute_to_output():
|
||||||
def test_geometry_nodes_contribute_to_output(source):
|
|
||||||
from tools.nodes.build import build_node_groups
|
from tools.nodes.build import build_node_groups
|
||||||
|
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
if source == "definition":
|
groups = build_node_groups()
|
||||||
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:
|
for group in groups:
|
||||||
if group.bl_idname != "GeometryNodeTree":
|
if group.bl_idname != "GeometryNodeTree":
|
||||||
continue
|
continue
|
||||||
@@ -199,17 +73,11 @@ def test_geometry_nodes_contribute_to_output(source):
|
|||||||
assert not unused, (group.name, unused)
|
assert not unused, (group.name, unused)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source", ["definition", "asset"])
|
def test_geometry_attribute_contract_includes_nested_groups():
|
||||||
def test_geometry_attribute_contract_includes_nested_groups(source):
|
|
||||||
from tools.nodes.build import build_node_groups
|
from tools.nodes.build import build_node_groups
|
||||||
|
|
||||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
if source == "definition":
|
pending = build_node_groups()
|
||||||
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()
|
seen = set()
|
||||||
allowed = {
|
allowed = {
|
||||||
"UVMap": ("FLOAT2", "CORNER"),
|
"UVMap": ("FLOAT2", "CORNER"),
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
|
|||||||
from tests.support.materials import image_layer
|
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('engine', ['CYCLES', 'BLENDER_EEVEE_NEXT'])
|
||||||
@pytest.mark.parametrize('domain', ['POINT', 'FACE'])
|
@pytest.mark.parametrize('domain', ['POINT', 'FACE'])
|
||||||
def test_normal_scale_preserves_missing_and_zero_weights(image_layer, engine, domain, tmp_path):
|
def test_normal_scale_preserves_missing_and_zero_weights(image_layer, engine, domain, tmp_path):
|
||||||
|
|||||||
@@ -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()
|
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'])
|
@pytest.mark.parametrize('engine', ['CYCLES', 'BLENDER_EEVEE_NEXT'])
|
||||||
def test_object_normals_follow_rotation_mirror_axis_and_wall_mask(image_layer, engine, tmp_path):
|
def test_object_normals_follow_rotation_mirror_axis_and_wall_mask(image_layer, engine, tmp_path):
|
||||||
scene = bpy.context.scene
|
scene = bpy.context.scene
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import bpy
|
|||||||
from anyimage.operators.cutout_tool.shape import NORMAL_REDUCTION_ATTRIBUTE_NAME
|
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 group_input as _group_input, interface_socket as _interface_socket
|
||||||
from ..common.nodes import store_float_attribute
|
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.reference_plane import reference_plane_depth
|
||||||
from ..common.boundary_smoothing import boundary_influence
|
from ..common.boundary_smoothing import boundary_influence
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user