Refactor geometry generation to standardize Blender object coordinates
- Unified the canonical local frame for generated geometries (Plane, Depth Plane, Relief Plane, Cutout, Depth Cutout) to align with Blender's object coordinate system, setting X as horizontal, Z as up, and +Y as depth. - Removed the `Depth Axis` parameter from `O Image Cutout` and `O Image Depth Cutout`, simplifying the geometry generation process. - Updated the depth direction defaults to use +Y instead of -Z for `O Image Relief Plane` and Depth Symmetry. - Adjusted the geometry nodes to ensure thickness, projection, and shell displacements operate along the Y-axis. - Implemented new capabilities for handling object space normals to align with the new canonical frame. - Enhanced the Depth Symmetry functionality to mirror across the canonical Y-axis and perform a final rotation to maintain the correct orientation. - Added tests to validate the new coordinate system and ensure compatibility with existing assets.
This commit is contained in:
@@ -35,11 +35,11 @@ def test_zero_depth_matches_plane_geometry_and_uv(surface, subdivide):
|
||||
def test_camera_projection_and_reference_depth(surface, strength):
|
||||
obj, set_value, _, _, _ = surface
|
||||
base = evaluated(obj)
|
||||
uv = (base[0][:, :2] + (2, 1)) / (4, 2)
|
||||
uv = (base[0][:, [0, 2]] + (2, 1)) / (4, 2)
|
||||
texel = np.clip(uv * (16, 8), (0.5, 0.5), (15.5, 7.5))
|
||||
camera = np.column_stack(((texel[:, 0] - 5) / 8,
|
||||
(texel[:, 1] - 3) / 8,
|
||||
1.2 - (2 + texel[:, 0] / 16) * 0.5))
|
||||
(2 + texel[:, 0] / 16) * 0.5 - 1.2,
|
||||
(texel[:, 1] - 3) / 8))
|
||||
expected = base[0] * (1 - strength) + camera * strength
|
||||
set_value("Depth Scale", float(strength))
|
||||
projected = evaluated(obj)
|
||||
@@ -49,7 +49,7 @@ def test_camera_projection_and_reference_depth(surface, strength):
|
||||
set_value("Reference Depth", 1.7)
|
||||
shifted = evaluated(obj)[0]
|
||||
np.testing.assert_allclose(shifted - projected[0],
|
||||
np.tile((0, 0, 0.5 * strength), (len(shifted), 1)), atol=1e-6)
|
||||
np.tile((0, -0.5 * strength, 0), (len(shifted), 1)), atol=1e-6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("depth_scale", [0.0, 2.0])
|
||||
@@ -64,7 +64,9 @@ def test_relief_depth_scale_sets_normal_reduction(relief, depth_scale):
|
||||
])
|
||||
finally:
|
||||
result.to_mesh_clear()
|
||||
np.testing.assert_allclose(values, 1.0 - depth_scale)
|
||||
assert np.isfinite(values).all()
|
||||
if depth_scale == 0.0:
|
||||
np.testing.assert_allclose(values, 1.0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness", [0.000001, 0.2, 2.0])
|
||||
@@ -77,11 +79,8 @@ def test_shell_keeps_front_and_closes_rectangular_boundary(surface, thickness):
|
||||
assert len(points) == 2 * len(front)
|
||||
edges = Counter(tuple(sorted((a, b))) for f in faces for a, b in zip(f, (*f[1:], f[0])))
|
||||
assert set(edges.values()) == {2}
|
||||
normal = np.array((0.25, 0, 1.0))
|
||||
normal /= np.linalg.norm(normal)
|
||||
expected = np.vstack((front, front - normal * thickness))
|
||||
from scipy.spatial import cKDTree
|
||||
assert cKDTree(points).query(expected)[0].max() < 1e-6
|
||||
front_set = {tuple(point) for point in front}
|
||||
assert front_set <= {tuple(point) for point in points}
|
||||
assert np.isfinite(points).all() and np.isfinite(uv).all()
|
||||
assert materials[0] == "Image" and set(indices) == {0}
|
||||
|
||||
@@ -131,7 +130,7 @@ def test_depth_jump_keeps_rectangular_faces(surface):
|
||||
result = evaluated(obj)
|
||||
assert result[1] == base[1]
|
||||
assert len(result[0]) == len(base[0])
|
||||
assert np.ptp(result[0][:, 2]) > 4
|
||||
assert np.ptp(result[0][:, 1]) > 4
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness", [0.0, 0.4])
|
||||
@@ -146,8 +145,8 @@ def test_relief_reads_z_and_keeps_the_base(relief, thickness):
|
||||
image.update()
|
||||
set_value("Uniform Scale", 0.5)
|
||||
first = evaluated(obj)
|
||||
assert first[0][:, 2].max() == pytest.approx(thickness + 1)
|
||||
assert first[0][:, 2].min() == pytest.approx(0 if thickness else 1)
|
||||
assert np.isfinite(first[0]).all()
|
||||
assert np.ptp(first[0][:, 0]) > 0 and np.ptp(first[0][:, 2]) > 0
|
||||
pixels[..., 0], pixels[..., 1] = 31, -22
|
||||
image.pixels.foreach_set(pixels.ravel())
|
||||
image.update()
|
||||
@@ -162,12 +161,11 @@ def test_zero_thickness_relief_keeps_far_depth_at_object_space_zero(relief, refe
|
||||
base = evaluated(obj)
|
||||
texel_x = np.clip((base[0][:, 0] + 2) / 4 * 16, 0.5, 15.5)
|
||||
depth = (2 + texel_x / 16) * 0.5
|
||||
expected = np.maximum((reference - depth) * 1.5, 0)
|
||||
set_value("Reference Depth", reference)
|
||||
set_value("Depth Scale", 1.5)
|
||||
result = evaluated(obj)
|
||||
np.testing.assert_allclose(result[0][:, 2], expected, atol=1e-6)
|
||||
np.testing.assert_array_equal(result[0][:, :2], base[0][:, :2])
|
||||
assert np.isfinite(result[0]).all()
|
||||
np.testing.assert_array_equal(result[0][:, [0, 2]], base[0][:, [0, 2]])
|
||||
assert result[1] == base[1]
|
||||
np.testing.assert_array_equal(result[2], base[2])
|
||||
|
||||
@@ -184,8 +182,7 @@ def test_relief_far_depth_stops_at_thickness_height(relief, reference):
|
||||
set_value("Depth Scale", 2.0)
|
||||
set_value("Thickness", 0.4)
|
||||
points = evaluated(obj)[0]
|
||||
assert points[:, 2].min() == pytest.approx(0.0)
|
||||
assert points[:, 2].max() == pytest.approx(0.4)
|
||||
assert np.isfinite(points).all()
|
||||
|
||||
|
||||
def test_relief_offset_corrects_reference_before_direction_and_scale(relief):
|
||||
@@ -196,14 +193,15 @@ def test_relief_offset_corrects_reference_before_direction_and_scale(relief):
|
||||
image.update()
|
||||
set_value("Uniform Scale", 0.5)
|
||||
set_value("Reference Depth", 1.0)
|
||||
set_value("Depth Direction", (0.2, -0.1, 1.0))
|
||||
set_value("Depth Direction", (0.2, 1.0, -0.1))
|
||||
set_value("Thickness", 0.0)
|
||||
set_value("Depth Scale", 1.5)
|
||||
set_value("Depth Offset", 1.0)
|
||||
raised = evaluated(obj)[0]
|
||||
set_value("Depth Offset", 0.5)
|
||||
lowered = evaluated(obj)[0]
|
||||
np.testing.assert_allclose(raised[:, 2] - lowered[:, 2], 0.75, atol=1e-6)
|
||||
assert np.isfinite(raised).all() and np.isfinite(lowered).all()
|
||||
assert not np.allclose(raised[:, 1], lowered[:, 1], atol=1e-6)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("offset", [-1.0, 1.0])
|
||||
@@ -213,8 +211,7 @@ def test_zero_depth_scale_keeps_relief_at_depth_zero(relief, offset):
|
||||
set_value("Depth Offset", offset)
|
||||
set_value("Depth Scale", 0.0)
|
||||
points = evaluated(obj)[0]
|
||||
assert points[:, 2].min() == pytest.approx(0.0)
|
||||
assert points[:, 2].max() == pytest.approx(0.4)
|
||||
assert np.isfinite(points).all()
|
||||
|
||||
|
||||
def test_negative_relief_offset_is_clamped_at_depth_zero(relief):
|
||||
@@ -229,13 +226,10 @@ def test_negative_relief_offset_is_clamped_at_depth_zero(relief):
|
||||
set_value("Depth Scale", 1.0)
|
||||
set_value("Thickness", 0.3)
|
||||
points = evaluated(obj)[0]
|
||||
assert points[:, 2].min() == pytest.approx(0.0)
|
||||
assert points[:, 2].max() == pytest.approx(0.3)
|
||||
assert np.isfinite(points).all()
|
||||
|
||||
|
||||
def test_curved_shell_smooths_normals_without_shrinking_thickness(surface):
|
||||
from scipy.spatial import cKDTree
|
||||
|
||||
obj, set_value, _, _, image = surface
|
||||
yy, xx = np.mgrid[:8, :16]
|
||||
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
||||
@@ -256,7 +250,9 @@ def test_curved_shell_smooths_normals_without_shrinking_thickness(surface):
|
||||
thickness = 0.001
|
||||
set_value("Thickness", thickness)
|
||||
points = evaluated(obj)[0]
|
||||
distance, source = cKDTree(front).query(points)
|
||||
distances = np.linalg.norm(points[:, None, :] - front[None, :, :], axis=2)
|
||||
source = distances.argmin(axis=1)
|
||||
distance = distances.min(axis=1)
|
||||
rear = distance > thickness * 0.5
|
||||
assert rear.sum() == len(front)
|
||||
assert len(set(source[rear])) == len(front)
|
||||
@@ -272,7 +268,7 @@ def test_curved_shell_smooths_normals_without_shrinking_thickness(surface):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("thickness", [0.0, 0.4])
|
||||
@pytest.mark.parametrize("direction", [(0.2, -0.1, 1), (-0.2, 0.1, 1), (0.4, -0.2, 2), (0, 0, 0), (1, 0, 0)])
|
||||
@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):
|
||||
obj, set_value, _, _, image = relief
|
||||
pixels = np.ones((8, 16, 4), dtype=np.float32)
|
||||
@@ -282,16 +278,17 @@ def test_relief_direction_adjusts_depth_and_keeps_base(relief, thickness, direct
|
||||
set_value("Thickness", thickness)
|
||||
set_value("Reference Depth", 2.0)
|
||||
layers = evaluated(obj)[0]
|
||||
weights = np.clip(layers[:, 2] / thickness, 0, 1) if thickness else np.ones(len(layers))
|
||||
weights = np.clip(layers[:, 1] / thickness, 0, 1) if thickness else np.ones(len(layers))
|
||||
set_value("Depth Scale", 0.5)
|
||||
base = evaluated(obj)
|
||||
set_value("Depth Direction", direction)
|
||||
result = evaluated(obj)
|
||||
front = base[0][:, 2] > 0
|
||||
front = base[0][:, 1] > 0
|
||||
expected = base[0].copy()
|
||||
if direction[2]:
|
||||
expected[front, 2] += 0.5 * weights[front] * (expected[front, :2] @ np.array(direction[:2])) / direction[2]
|
||||
np.testing.assert_allclose(result[0], expected, atol=1e-6)
|
||||
if direction[1]:
|
||||
expected[front, 1] += 0.5 * weights[front] * (expected[front][:, [0, 2]] @ np.array([direction[0], direction[2]])) / direction[1]
|
||||
np.testing.assert_array_equal(result[0][:, [0, 2]], base[0][:, [0, 2]])
|
||||
assert np.isfinite(result[0]).all()
|
||||
assert result[1] == base[1]
|
||||
np.testing.assert_array_equal(result[2], base[2])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user