181 lines
7.0 KiB
Python
181 lines
7.0 KiB
Python
"""Behavior checks for the Depth Cutout balloon and shell shapes."""
|
|
import bpy
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from anyimage.common.object import modifier_input_identifier, set_modifier_input
|
|
|
|
|
|
@pytest.mark.parametrize("mode", [0, 1])
|
|
def test_edge_turn_reshapes_thickness_without_moving_zero_thickness(mode):
|
|
"""Edge Turn swings the wall direction and leaves zero thickness untouched."""
|
|
from tests.support.depth_surface import surface, evaluated
|
|
|
|
obj, set_value = surface(step=1, resolution=10)
|
|
xz = np.array([(v.co.x, v.co.z) for v in obj.data.vertices])
|
|
profile = .3 * np.sin(np.pi * xz[:, 0] / 2) * np.sin(np.pi * xz[:, 1])
|
|
profile[abs(profile) < 1e-7] = 0
|
|
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set("value", profile)
|
|
set_value("Mode", mode)
|
|
set_value("Front Inflation", .2)
|
|
modifier = obj.modifiers[0]
|
|
thickness = modifier_input_identifier(modifier.node_group, "Thickness", subtype="DISTANCE" if mode else "NONE")
|
|
turn = modifier_input_identifier(modifier.node_group, "Edge Turn")
|
|
|
|
def result(amount, edge_turn):
|
|
set_modifier_input(modifier, thickness, amount)
|
|
set_modifier_input(modifier, turn, edge_turn)
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
return evaluated(obj)
|
|
|
|
plain, faces = result(0, 0)
|
|
turned, turned_faces = result(0, 2)
|
|
np.testing.assert_array_equal(plain, turned)
|
|
assert faces == turned_faces
|
|
plain, faces = result(1, 0)
|
|
turned, turned_faces = result(1, 2)
|
|
assert faces == turned_faces
|
|
assert np.isfinite(turned).all()
|
|
assert np.max(np.abs(plain - turned)) > 1e-5
|
|
|
|
|
|
def test_shell_offset_scales_with_thickness_and_ignores_reference():
|
|
"""A thin Shell moves a distance proportional to its thickness."""
|
|
from tests.support.depth_surface import surface, evaluated
|
|
|
|
obj, set_value = surface(step=0, resolution=12)
|
|
image = bpy.data.images['Camera']
|
|
pixels = np.array(image.pixels[:], dtype=np.float32).reshape(32, 64, 4)
|
|
pixels[..., 2] = 1 + .15 * np.sin(pixels[..., 0] * 6) * np.cos(pixels[..., 1] * 5)
|
|
image.pixels.foreach_set(pixels.ravel())
|
|
image.update()
|
|
set_value('Reference Depth', 10.)
|
|
set_value('Thickness', 0.)
|
|
front, _ = evaluated(obj)
|
|
|
|
def offsets(thickness, reference=10.):
|
|
set_value('Thickness', thickness)
|
|
set_value('Reference Depth', reference)
|
|
points, faces = evaluated(obj)
|
|
adjusted_front = front + (0, 10 - reference, 0)
|
|
from scipy.spatial import cKDTree
|
|
distances, indices = cKDTree(adjusted_front).query(points)
|
|
np.testing.assert_allclose(np.sort(distances)[:len(front)], 0, atol=2e-6)
|
|
rear = distances > thickness * .5
|
|
assert rear.sum() == len(front)
|
|
order = np.argsort(indices[rear])
|
|
return ((points[rear] - adjusted_front[indices[rear]]) / thickness)[order], faces
|
|
|
|
smooth, faces = offsets(.02)
|
|
thinner, thinner_faces = offsets(.01)
|
|
distant, _ = offsets(.02, 20.)
|
|
np.testing.assert_allclose(thinner, smooth, atol=2e-4)
|
|
np.testing.assert_allclose(distant, smooth, atol=2e-4)
|
|
assert thinner_faces == faces
|
|
|
|
|
|
def test_shell_thickness_extends_behind_the_tilted_front():
|
|
from tests.support.depth_surface import surface, evaluated
|
|
|
|
obj, set_value = surface(step=0, resolution=4)
|
|
image = bpy.data.images["Camera"]
|
|
pixels = np.array(image.pixels[:], dtype=np.float32).reshape(32, 64, 4)
|
|
pixels[..., 2] = 1 + 0.4 * pixels[..., 0]
|
|
image.pixels.foreach_set(pixels.ravel())
|
|
image.update()
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
front, _ = evaluated(obj)
|
|
set_value("Thickness", 0.2)
|
|
set_value("Front Inflation", 0.5)
|
|
points, _ = evaluated(obj)
|
|
front_set = {tuple(point) for point in front}
|
|
assert front_set <= {tuple(point) for point in points}
|
|
assert not np.allclose(points[len(front) :], front, atol=1e-6)
|
|
|
|
|
|
def test_balloon_shell_converges_to_front_with_thickness():
|
|
from scipy.spatial import cKDTree
|
|
from tests.support.depth_surface import surface, evaluated
|
|
|
|
obj, set_value = surface(step=1, resolution=10)
|
|
xz = np.array([(v.co.x, v.co.z) for v in obj.data.vertices])
|
|
profile = 0.3 * np.sin(np.pi * xz[:, 0] / 2) * np.sin(np.pi * xz[:, 1])
|
|
profile[abs(profile) < 1e-7] = 0
|
|
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set("value", profile)
|
|
set_value("Mode", 0)
|
|
modifier = obj.modifiers[0]
|
|
thickness = modifier_input_identifier(modifier.node_group, "Thickness", subtype="NONE")
|
|
|
|
def amount(value):
|
|
set_modifier_input(modifier, thickness, value)
|
|
obj.update_tag(refresh={"DATA"})
|
|
bpy.context.view_layer.update()
|
|
return evaluated(obj)
|
|
|
|
front, _ = amount(0)
|
|
tree = cKDTree(front)
|
|
gaps = []
|
|
for value in (0.01, 0.001, 0.0001):
|
|
points, _ = amount(value)
|
|
gaps.append(tree.query(points)[0].max())
|
|
assert gaps[0] > gaps[1] > gaps[2]
|
|
np.testing.assert_allclose(gaps[1] / gaps[0], 0.1, rtol=0.02)
|
|
|
|
|
|
def test_depth_cutout_normal_reduction_uses_point_domain():
|
|
from tests.support.depth_surface import surface
|
|
|
|
obj, set_value = surface()
|
|
set_value("Thickness", 0.3)
|
|
bpy.context.view_layer.update()
|
|
evaluated = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
mesh = evaluated.to_mesh()
|
|
try:
|
|
attribute = mesh.attributes["o_normal_reduction"]
|
|
assert attribute.domain == "POINT"
|
|
values = [item.value for item in attribute.data]
|
|
assert 0.0 in values and 1.0 in values
|
|
assert all(np.isfinite(values))
|
|
finally:
|
|
evaluated.to_mesh_clear()
|
|
|
|
|
|
def test_zero_thickness_short_circuits_shell_without_warnings():
|
|
from tests.support.depth_surface import surface, evaluated
|
|
|
|
obj, set_value = surface()
|
|
set_value("Thickness", 0.0)
|
|
points, faces = evaluated(obj)
|
|
assert len(faces) == len(obj.data.polygons)
|
|
modifier = obj.modifiers[0]
|
|
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
|
result.to_mesh()
|
|
try:
|
|
assert not list(modifier.node_warnings), [
|
|
warning.message for warning in modifier.node_warnings
|
|
]
|
|
finally:
|
|
result.to_mesh_clear()
|
|
|
|
|
|
@pytest.mark.parametrize("thickness,inflation", [
|
|
(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
|
|
|
|
obj, set_value = surface(step=0, resolution=10)
|
|
xz = np.array([(v.co.x, v.co.z) for v in obj.data.vertices])
|
|
profile = 0.3 * np.sin(np.pi * xz[:, 0] / 2) * np.sin(np.pi * xz[:, 1])
|
|
profile[abs(profile) < 1e-7] = 0
|
|
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set("value", profile)
|
|
set_value("Thickness", thickness)
|
|
set_value("Front Inflation", 0.0)
|
|
baseline, baseline_faces = evaluated(obj)
|
|
set_value("Front Inflation", inflation)
|
|
inflated, inflated_faces = evaluated(obj)
|
|
assert baseline_faces == inflated_faces
|
|
np.testing.assert_array_equal(inflated, baseline)
|