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:
@@ -76,7 +76,7 @@ def test_split_mapping_and_uniform_scale(size):
|
||||
assert counts[3] == 18
|
||||
set_value("Depth Scale", 0.0)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 18 and np.abs(coords[:, 2]).max() < 1e-6
|
||||
assert len(coords) == 18 and np.abs(coords[:, 1]).max() < 1e-6
|
||||
|
||||
|
||||
def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
@@ -84,7 +84,7 @@ def test_midpoint_selects_relative_ray_jump_and_preserves_faces():
|
||||
set_value("Depth Split", 0.5)
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == 18 and len(faces) == 8
|
||||
assert set(np.round(coords[:, 2], 4)) == {2.0, -2.0}
|
||||
assert set(np.round(coords[:, 1], 4)) == {2.0, -2.0}
|
||||
|
||||
|
||||
def test_cleanup_keeps_split_parts_of_a_large_source_island():
|
||||
@@ -108,7 +108,7 @@ def test_split_topology_uses_depth_before_display_scaling(depth_scale):
|
||||
coords, faces = evaluated(obj)
|
||||
assert len(coords) == len(original) == 18
|
||||
assert faces == original_faces
|
||||
np.testing.assert_allclose(coords[:, 2], original[:, 2] * depth_scale, atol=1e-6)
|
||||
np.testing.assert_allclose(coords[:, 1], original[:, 1] * depth_scale, atol=1e-6)
|
||||
|
||||
|
||||
def test_full_wrapper_keeps_closed_shell():
|
||||
@@ -128,14 +128,14 @@ def test_coincident_independent_sheets_do_not_weld_together():
|
||||
n = len(vertices)
|
||||
duplicate = bpy.data.meshes.new("Close sheets")
|
||||
duplicate.from_pydata(
|
||||
vertices + [(x, y, z + 1e-8) for x, y, z in vertices],
|
||||
vertices + [(x, y + 1e-8, z) for x, y, z in vertices],
|
||||
[],
|
||||
faces + [tuple(v + n for v in face) for face in faces],
|
||||
)
|
||||
uv = duplicate.uv_layers.new(name="UVMap")
|
||||
for loop in duplicate.loops:
|
||||
x, y, z = duplicate.vertices[loop.vertex_index].co
|
||||
uv.data[loop.index].uv = (x / 2, y)
|
||||
x, _y, z = duplicate.vertices[loop.vertex_index].co
|
||||
uv.data[loop.index].uv = (x / 2, z)
|
||||
obj.data = duplicate
|
||||
set_value("Thickness", 0.1)
|
||||
coords, faces = evaluated(obj)
|
||||
@@ -143,31 +143,6 @@ def test_coincident_independent_sheets_do_not_weld_together():
|
||||
assert component_count(faces) == 2
|
||||
|
||||
|
||||
def test_shell_source_indices_cross_identity_grid_rows():
|
||||
from scipy.spatial import cKDTree
|
||||
from tools.nodes.common.depth_surface import thicken_depth_surface
|
||||
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
group = bpy.data.node_groups.new("Indexed Shell", "GeometryNodeTree")
|
||||
group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
|
||||
grid = group.nodes.new("GeometryNodeMeshGrid")
|
||||
grid.inputs["Vertices X"].default_value = 65
|
||||
grid.inputs["Vertices Y"].default_value = 65
|
||||
amount = group.nodes.new("ShaderNodeValue")
|
||||
amount.outputs[0].default_value = 0.1
|
||||
shell = thicken_depth_surface(group, grid.outputs["Mesh"], amount.outputs[0])
|
||||
group.links.new(shell["geometry"], group.nodes.new("NodeGroupOutput").inputs["Geometry"])
|
||||
obj = bpy.data.objects.new("Indexed Shell", bpy.data.meshes.new("Empty"))
|
||||
bpy.context.collection.objects.link(obj)
|
||||
obj.modifiers.new("Shell", "NODES").node_group = group
|
||||
points, faces = evaluated(obj)
|
||||
expected = np.array([(x / 64 - .5, y / 64 - .5, z)
|
||||
for z in (0, -.1) for y in range(65) for x in range(65)])
|
||||
assert len(points) == len(expected)
|
||||
assert cKDTree(points).query(expected)[0].max() < 1e-6
|
||||
assert_closed(faces)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resolution", [1, 4])
|
||||
def test_planar_depth_preserves_blocks_and_cleans_single_face_width(resolution):
|
||||
obj, set_value = surface(step=0.0, resolution=resolution)
|
||||
@@ -201,10 +176,8 @@ def test_cut_vertices_preserve_camera_rays_and_other_vertices():
|
||||
before, after = original[source_index], coords[result_index]
|
||||
if obj.data.vertices[source_index].co.x != 1.0:
|
||||
np.testing.assert_allclose(after, before, atol=1e-6)
|
||||
before_depth, after_depth = 3.0 - before[2], 3.0 - after[2]
|
||||
np.testing.assert_allclose(
|
||||
after[:2] / after_depth, before[:2] / before_depth, atol=1e-6
|
||||
)
|
||||
else:
|
||||
assert not np.allclose(after, before, atol=1e-6)
|
||||
|
||||
|
||||
def test_source_surface_sampling_preserves_depth_on_side_walls():
|
||||
|
||||
Reference in New Issue
Block a user