feat: implement separate geometry handling for panorama mask regions
- Replace Delete Geometry with Separate Geometry in O Image Depth Panorama to manage low and high mask areas independently. - Adjust depth processing to ensure low mask regions are displaced to Dome Radius while high mask regions follow existing depth rules. - Update tests to validate new behavior, ensuring outputs align with expected results for both mask states. - Enhance material handling by introducing emission strength in the shadeless group.
This commit is contained in:
@@ -49,6 +49,11 @@ def evaluated(obj):
|
||||
result.to_mesh_clear()
|
||||
|
||||
|
||||
def free_edges(faces):
|
||||
edges = Counter(tuple(sorted((a, b))) for face in faces for a, b in zip(face, (*face[1:], face[0])))
|
||||
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):
|
||||
@@ -71,11 +76,13 @@ def test_quad_sphere_topology_and_radial_endpoints(subdivide, dome):
|
||||
assert np.max(np.ptp(uv.reshape(-1, 4, 2)[..., 0], axis=1)) <= 0.5 + 1e-5
|
||||
|
||||
|
||||
def test_mask_threshold_culls_faces():
|
||||
def test_depth_mask_keeps_only_the_faces_above_the_threshold():
|
||||
obj, set_value = panorama(alpha=0.5)
|
||||
assert not len(evaluated(obj)[0])
|
||||
set_value("Depth Mask", False)
|
||||
assert len(evaluated(obj)[1]) == 384
|
||||
points, faces, _ = evaluated(obj)
|
||||
assert len(faces) == 384
|
||||
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 50, atol=1e-5)
|
||||
set_value("Depth Mask", True)
|
||||
set_value("Mask Threshold", 0.49)
|
||||
assert len(evaluated(obj)[1]) == 384
|
||||
@@ -115,8 +122,8 @@ def test_linear_depth_reconstructs_flat_floor_without_sampling_rings():
|
||||
assert np.max(abs(error)) < 0.004
|
||||
|
||||
|
||||
def test_masked_area_falls_back_to_the_dome_radius():
|
||||
"""The mask decides validity; what Depth Mask keeps lands on the dome sphere."""
|
||||
def test_masked_area_rests_on_the_dome_sphere():
|
||||
"""Depth Mask keeps only the valid half; the excluded half rests on the dome."""
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
depth = np.where(alpha, 3.0, 1e-7)
|
||||
@@ -140,6 +147,70 @@ def test_masked_area_falls_back_to_the_dome_radius():
|
||||
np.testing.assert_allclose(np.linalg.norm(evaluated(obj)[0], axis=1), 50, atol=1e-3)
|
||||
|
||||
|
||||
def test_masked_region_ignores_its_own_depth_and_depth_scale():
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
depth = np.where(alpha, 3.0, 1e4)
|
||||
obj, set_value = panorama(depth, alpha, subdivide=4)
|
||||
set_value("Depth Mask", False)
|
||||
for scale in (0.0, 1.0):
|
||||
set_value("Depth Scale", scale)
|
||||
radii = np.linalg.norm(evaluated(obj)[0], axis=1)
|
||||
on_dome = np.isclose(radii, 50, atol=1e-3)
|
||||
assert on_dome.any()
|
||||
assert np.all(on_dome | np.isclose(radii, 3.0, atol=1e-3))
|
||||
|
||||
|
||||
def test_masked_regions_keep_their_own_seam_edges():
|
||||
"""Both regions reach the output, so the seam is a free edge on each side."""
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
plain, _ = panorama(3.0, 1.0, subdivide=4)
|
||||
whole, whole_faces, _ = evaluated(plain)
|
||||
obj, set_value = panorama(3.0, alpha, subdivide=4)
|
||||
set_value("Depth Mask", False)
|
||||
points, faces, _ = evaluated(obj)
|
||||
assert len(faces) == len(whole_faces)
|
||||
assert len(points) > len(whole)
|
||||
set_value("Depth Mask", True)
|
||||
assert free_edges(faces) == 2 * free_edges(evaluated(obj)[1])
|
||||
|
||||
|
||||
def test_boundary_smooth_relaxes_both_masked_region_boundaries():
|
||||
"""Boundary Smooth relaxes both regions at the seam and leaves their interiors."""
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
depth = np.where(alpha, 3.0, 1e4)
|
||||
depth[:, 56] = 30.0
|
||||
obj, set_value = panorama(depth, alpha, subdivide=4)
|
||||
set_value("Depth Mask", False)
|
||||
before = evaluated(obj)[0]
|
||||
low = np.isclose(np.linalg.norm(before, axis=1), 50, atol=1e-3)
|
||||
assert low.any()
|
||||
set_value("Boundary Smooth", 5)
|
||||
after = evaluated(obj)[0]
|
||||
moved = np.linalg.norm(after - before, axis=1) > 1e-6
|
||||
assert moved[low].any() and moved[~low].any()
|
||||
assert not moved[low].all()
|
||||
np.testing.assert_allclose(np.linalg.norm(after[low & ~moved], axis=1), 50, atol=1e-5)
|
||||
|
||||
|
||||
def test_flat_scale_without_mask_returns_a_whole_sphere():
|
||||
"""Depth Scale 0 without the mask leaves the dome sphere uncut and unsplit."""
|
||||
alpha = np.ones((64, 128))
|
||||
alpha[:, 64:] = 0
|
||||
plain, _ = panorama(3.0, 1.0, subdivide=4)
|
||||
whole, whole_faces, _ = evaluated(plain)
|
||||
obj, set_value = panorama(np.where(alpha, 3.0, 1e4), alpha, subdivide=4)
|
||||
set_value("Depth Mask", False)
|
||||
set_value("Depth Scale", 0)
|
||||
points, faces, _ = evaluated(obj)
|
||||
assert len(faces) == len(whole_faces)
|
||||
assert len(points) == len(whole)
|
||||
assert free_edges(faces) == 0
|
||||
np.testing.assert_allclose(np.linalg.norm(points, axis=1), 50, atol=1e-6)
|
||||
|
||||
|
||||
def test_controls_and_material_slot_inheritance():
|
||||
obj, set_value = panorama()
|
||||
group = obj.modifiers[0].node_group
|
||||
|
||||
Reference in New Issue
Block a user