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:
2026-09-15 13:33:37 +08:00
parent 591e62085a
commit 4035f5c9e4
47 changed files with 892 additions and 570 deletions

View File

@@ -25,8 +25,8 @@ def test_normal_smooth_changes_thickness_direction_without_moving_zero_thickness
from tests.support.depth_surface import surface, evaluated
obj, set_value = surface(step=1, resolution=10)
xy = np.array([v.co[:2] for v in obj.data.vertices])
profile = .3 * np.sin(np.pi * xy[:, 0] / 2) * np.sin(np.pi * xy[:, 1])
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)
@@ -68,7 +68,7 @@ def test_thin_shell_normal_smoothing_is_independent_of_thickness_and_reference()
set_value('Normal Smooth', smoothing)
set_value('Reference Depth', reference)
points, faces = evaluated(obj)
adjusted_front = front + (0, 0, reference - 10.)
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)
@@ -117,9 +117,9 @@ def test_shell_thickness_extends_behind_the_tilted_front():
set_value("Thickness", 0.2)
set_value("Front Inflation", 0.5)
points, faces = evaluated(obj)
expected = front - 0.2 * np.array((0.4, 0, 1)) / np.sqrt(1.16)
assert cKDTree(points).query(expected)[0].max() < 1e-5
assert cKDTree(points).query(front)[0].max() < 1e-5
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)
assert_closed(faces)
@@ -127,8 +127,8 @@ def test_balloon_shell_converges_to_front_with_thickness():
from scipy.spatial import cKDTree
from tests.support.depth_surface import surface, evaluated, assert_closed
obj, set_value = surface(step=1, resolution=10)
xy = np.array([v.co[:2] for v in obj.data.vertices])
profile = 0.3 * np.sin(np.pi * xy[:, 0] / 2) * np.sin(np.pi * xy[:, 1])
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)
@@ -162,7 +162,7 @@ def test_smooth_back_rejects_front_noise_and_preserves_center_depth():
faces = [(y*n+x, y*n+x+1, (y+1)*n+x+1) for y in range(n-1) for x in range(n-1)]
faces += [(y*n+x, (y+1)*n+x+1, (y+1)*n+x) for y in range(n-1) for x in range(n-1)]
mesh = bpy.data.meshes.new("Noisy front")
mesh.from_pydata(np.column_stack((points, center + profile + noise)), [], faces)
mesh.from_pydata(np.column_stack((points[:, 0], center + profile + noise, points[:, 1])), [], faces)
mesh.attributes.new("o_balloon", "FLOAT", "POINT").data.foreach_set("value", profile.astype(np.float32))
obj = bpy.data.objects.new("Noisy front", mesh)
bpy.context.collection.objects.link(obj)
@@ -215,8 +215,8 @@ def test_depth_cutout_normal_map_only_affects_front_faces():
def test_shell_ignores_front_inflation(thickness, inflation):
from tests.support.depth_surface import surface, evaluated, assert_closed
obj, set_value = surface(step=0, resolution=10)
xy = np.array([v.co[:2] for v in obj.data.vertices])
profile = 0.3 * np.sin(np.pi * xy[:, 0] / 2) * np.sin(np.pi * xy[:, 1])
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)
@@ -234,8 +234,8 @@ def test_balloon_suppression_is_stronger_without_a_startup_threshold(monkeypatch
from tests.support.depth_surface import surface, evaluated
set_test_structure(monkeypatch, 1)
obj, set_value = surface(step=0, resolution=10)
xy = np.array([v.co[:2] for v in obj.data.vertices])
profile = 0.3 * np.sin(np.pi * xy[:, 0] / 2) * np.sin(np.pi * xy[:, 1])
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)
group = obj.modifiers[0].node_group