refactor: incorporate Depth Scale into Depth Split criteria for consistent geometry handling
This commit is contained in:
@@ -85,7 +85,9 @@ def diagonal_surface(triangles=False):
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Depth Split", 0.5)
|
||||
set_value("Depth Scale", 0)
|
||||
# A shallow display keeps the cut nearly flat so smoothing cannot fold the
|
||||
# sawtooth slivers this fixture exists to exercise.
|
||||
set_value("Depth Scale", 0.05)
|
||||
return obj, set_value
|
||||
|
||||
|
||||
@@ -201,7 +203,6 @@ def test_smoothing_reduces_stairs_without_leaving_two_ring_band(triangles):
|
||||
before.append(np.linalg.norm(original[adjacent].mean(axis=0) - original[i]))
|
||||
after.append(np.linalg.norm(smoothed[adjacent].mean(axis=0) - smoothed[i]))
|
||||
assert sum(after) < 0.8 * sum(before)
|
||||
np.testing.assert_array_equal(smoothed[:, 1], original[:, 1])
|
||||
set_value("Boundary Smooth", 0)
|
||||
np.testing.assert_array_equal(evaluated(obj)[0], original)
|
||||
|
||||
@@ -393,4 +394,3 @@ def test_junction_band_keeps_the_sharpness_weight():
|
||||
creased_step = abs(creased[4][1] + 0.5)
|
||||
assert flat_step > 1e-3
|
||||
assert creased_step < flat_step * 0.99
|
||||
|
||||
|
||||
@@ -20,6 +20,18 @@ def profile_values(obj):
|
||||
result.to_mesh_clear()
|
||||
|
||||
|
||||
def vertex_uv(obj):
|
||||
result = obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
|
||||
mesh = result.to_mesh()
|
||||
try:
|
||||
uv = np.zeros((len(mesh.vertices), 2))
|
||||
for loop in mesh.loops:
|
||||
uv[loop.vertex_index] = mesh.uv_layers["UVMap"].data[loop.index].uv
|
||||
return uv
|
||||
finally:
|
||||
result.to_mesh_clear()
|
||||
|
||||
|
||||
def test_boundary_triangle_cleanup_is_one_pass():
|
||||
obj, _ = surface(resolution=2)
|
||||
group = obj.modifiers[0].node_group
|
||||
@@ -62,12 +74,14 @@ def test_split_profile_tapers_within_boundary_band(resolution, aspect):
|
||||
image.update()
|
||||
obj.data.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set(
|
||||
"value", np.full(len(obj.data.vertices), .3, dtype=np.float32))
|
||||
set_value("Depth Scale", 0)
|
||||
set_value("Depth Scale", 1)
|
||||
np.testing.assert_allclose(profile_values(obj), .3)
|
||||
set_value("Depth Split", .1)
|
||||
points, faces = evaluated(obj)
|
||||
boundary = boundary_neighbors(faces)
|
||||
cut = [i for i in boundary if 0 < points[i,2] < 1 and .5*aspect < points[i,0] < 1.5*aspect]
|
||||
uv = vertex_uv(obj)
|
||||
# The depth step sits at u=0.5, so that column is the split line.
|
||||
cut = [i for i in boundary if abs(uv[i,0] - .5) < 1e-4 and 1e-4 < uv[i,1] < 1 - 1e-4]
|
||||
assert cut
|
||||
band = edge_band(faces, cut)
|
||||
outside = [i for i in range(len(points)) if i not in band]
|
||||
|
||||
@@ -131,6 +131,26 @@ def test_depth_split_preserves_faces_and_closes_each_shell(surface, valid_only):
|
||||
assert restored[1] == base[1]
|
||||
|
||||
|
||||
def test_depth_scale_gates_depth_split(surface):
|
||||
obj, set_value, _, _, image = surface
|
||||
pixels = np.asarray(image.pixels[:], dtype=np.float32).reshape(8, 16, 4)
|
||||
pixels[:, 8:, 2] += 10
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
set_value("Depth Scale", 1.0)
|
||||
whole = evaluated(obj)
|
||||
set_value("Depth Split", 1.0)
|
||||
split = evaluated(obj)
|
||||
assert len(split[0]) > len(whole[0])
|
||||
set_value("Depth Scale", 0.0)
|
||||
set_value("Depth Split", 0.0)
|
||||
flat = evaluated(obj)
|
||||
set_value("Depth Split", 1.0)
|
||||
cancelled = evaluated(obj)
|
||||
assert cancelled[1] == flat[1]
|
||||
np.testing.assert_allclose(cancelled[0], flat[0], atol=1e-6)
|
||||
|
||||
|
||||
def test_depth_jump_keeps_rectangular_faces(surface):
|
||||
obj, set_value, _, _, image = surface
|
||||
base = evaluated(obj)
|
||||
|
||||
@@ -132,6 +132,7 @@ def test_zero_split_bypasses_cleanup_and_positive_split_can_empty_mesh(kind):
|
||||
|
||||
obj, set_value, _, _, _ = create_surface("DEPTH")
|
||||
set_value("Subdivide", 0)
|
||||
set_value("Depth Scale", 1.0)
|
||||
original, faces = evaluated(obj)
|
||||
assert len(faces) == 2
|
||||
set_value("Depth Split", 0.001)
|
||||
|
||||
@@ -75,8 +75,8 @@ def test_split_mapping_and_uniform_scale(size):
|
||||
assert counts[:3] == [15] * 3
|
||||
assert counts[3] == 18
|
||||
set_value("Depth Scale", 0.0)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 18 and np.abs(coords[:, 1]).max() < 1e-6
|
||||
coords, _ = evaluated(obj)
|
||||
assert len(coords) == counts[0] and np.abs(coords[:, 1]).max() < 1e-6
|
||||
|
||||
|
||||
def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
@@ -87,16 +87,33 @@ def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
assert set(np.round(coords[:, 1], 4)) == {2.0, -2.0}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth_scale", [0.0, 2.0])
|
||||
def test_split_topology_uses_depth_before_display_scaling(depth_scale):
|
||||
@pytest.mark.parametrize("depth_scale", [0.0, 1.0])
|
||||
def test_depth_scale_scales_split_selection(depth_scale):
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Depth Split", 0.5)
|
||||
original, original_faces = evaluated(obj)
|
||||
set_value("Depth Scale", depth_scale)
|
||||
set_value("Depth Split", 0.0)
|
||||
whole, whole_faces = evaluated(obj)
|
||||
set_value("Depth Split", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == len(original) == 18
|
||||
assert faces == original_faces
|
||||
np.testing.assert_allclose(coords[:, 1], original[:, 1] * depth_scale, atol=1e-6)
|
||||
assert np.isfinite(coords).all()
|
||||
if depth_scale == 0.0:
|
||||
# A flat projection keeps every face, so there is nothing to separate.
|
||||
np.testing.assert_allclose(coords, whole, atol=1e-6)
|
||||
assert faces == whole_faces
|
||||
else:
|
||||
assert len(coords) > len(whole)
|
||||
|
||||
|
||||
def test_depth_scale_multiplies_split_strength():
|
||||
obj, set_value = surface(step=4.0)
|
||||
set_value("Depth Scale", 0.5)
|
||||
set_value("Depth Split", 1.0)
|
||||
scaled, scaled_faces = evaluated(obj)
|
||||
set_value("Depth Scale", 1.0)
|
||||
set_value("Depth Split", 0.5)
|
||||
reference, reference_faces = evaluated(obj)
|
||||
assert len(scaled) == len(reference) > 15
|
||||
assert scaled_faces == reference_faces
|
||||
|
||||
|
||||
def test_full_wrapper_keeps_closed_shell():
|
||||
@@ -314,6 +331,7 @@ def test_split_uses_ray_distance_and_ignores_absolute_distance_scale(kind):
|
||||
if kind == "plane":
|
||||
obj, set_value, _, _, image = create_surface("DEPTH")
|
||||
set_value("Subdivide", 1)
|
||||
set_value("Depth Scale", 1)
|
||||
else:
|
||||
obj, set_value = surface(step=0)
|
||||
image = bpy.data.images["Camera"]
|
||||
|
||||
@@ -194,6 +194,21 @@ def test_material_uv_matches_radial_sampling_and_faces_point_inward():
|
||||
result.to_mesh_clear()
|
||||
|
||||
|
||||
def test_depth_scale_gates_split_selection():
|
||||
depth = np.ones((64, 128))
|
||||
depth[:, 64:] = 7
|
||||
obj, set_value = panorama(depth)
|
||||
set_value("Depth Scale", 0)
|
||||
whole = evaluated(obj)
|
||||
set_value("Depth Split", 1)
|
||||
flat = evaluated(obj)
|
||||
# A flat projection keeps the sphere whole, whatever the split strength is.
|
||||
assert flat[1] == whole[1]
|
||||
np.testing.assert_allclose(flat[0], whole[0], atol=1e-6)
|
||||
set_value("Depth Scale", 1)
|
||||
assert len(evaluated(obj)[0]) > len(flat[0])
|
||||
|
||||
|
||||
def test_split_strength_and_distance_scale_invariance():
|
||||
depth = np.ones((64, 128))
|
||||
depth[:, 64:] = 7
|
||||
|
||||
@@ -14,6 +14,7 @@ def test_depth_attribute_cleanup_has_no_warnings(kind):
|
||||
obj, set_value = surface()
|
||||
elif kind == "plane":
|
||||
obj, set_value, *_ = create_surface("DEPTH")
|
||||
set_value("Depth Scale", 1)
|
||||
else:
|
||||
obj, set_value = panorama(subdivide=2)
|
||||
modifier = obj.modifiers[0]
|
||||
|
||||
Reference in New Issue
Block a user