Add new functionality for image rectification and depth metadata handling

- Implemented a preview tool for rectifying images with interactive aspect controls in `preview.py`.
- Added tests for depth metadata loading to ensure consistent schema usage in `test_depth_metadata.py`.
- Developed comprehensive tests for cutout calibration processes in `test_cutout_calibration.py`, including stability checks and depth calibration.
- Created tests for frame compositing operations in `test_frame_compositing.py`, ensuring correct handling of depth and transparency.
- Introduced material slot inheritance functionality in `material_slots.py` to manage geometry material assignments.
- Built shared shader nodes for material processing in `shader.py`, including roughness and bump mapping setups.
This commit is contained in:
2026-09-12 19:20:06 +08:00
parent e8268504c2
commit 938d95db0f
175 changed files with 3812 additions and 6864 deletions

View File

@@ -8,15 +8,11 @@ import pytest
from anyimage.common.object import modifier_input_identifier, set_modifier_input
from tools.nodes.groups.image_depth_panorama import build_image_depth_panorama_group
from tests.support.nodes import validate_node_structure
def panorama(depth=3, alpha=1, subdivide=3):
bpy.ops.wm.read_factory_settings(use_empty=True)
group = build_image_depth_panorama_group()
assert group.name == "O Image Depth Panorama"
validate_node_structure(group)
assert not any(node.bl_idname == "GeometryNodeGroup" for node in group.nodes)
image = bpy.data.images.new("Radial Depth", width=128, height=64, float_buffer=True)
image.colorspace_settings.name = "Non-Color"
image.alpha_mode = "CHANNEL_PACKED"
@@ -37,6 +33,7 @@ def panorama(depth=3, alpha=1, subdivide=3):
for name, value in (("Depth Image", image), ("Subdivide", subdivide)):
set_value(name, value)
set_value("Boundary Smooth", 0)
set_value("Depth Split", 0)
return obj, set_value
@@ -47,7 +44,6 @@ def evaluated(obj):
points = np.array([v.co[:] for v in mesh.vertices]).reshape(-1, 3)
faces = [tuple(f.vertices) for f in mesh.polygons]
uv = np.array([v.uv[:] for v in mesh.uv_layers.active.data]) if mesh.uv_layers.active else np.empty((0, 2))
assert not any(a.name.startswith(("o_depth_", "o_panorama_")) for a in mesh.attributes)
return points, faces, uv
finally:
result.to_mesh_clear()
@@ -75,10 +71,10 @@ def test_quad_sphere_topology_and_radial_endpoints(subdivide):
def test_depth_alpha_culls_faces_without_double_multiplication():
obj, set_value = panorama(alpha=0.5)
assert not len(evaluated(obj)[0])
set_value("Valid Only", False)
set_value("Depth Mask", False)
assert len(evaluated(obj)[1]) == 384
set_value("Valid Only", True)
set_value("Validity Threshold", 0.49)
set_value("Depth Mask", True)
set_value("Mask Threshold", 0.49)
assert len(evaluated(obj)[1]) == 384
@@ -115,7 +111,7 @@ def test_invalid_distance_removes_faces_between_valid_and_invalid_regions(sky_al
assert np.linalg.norm(points, axis=1).max() < 3.01
set_value("Invalid Distance", 250)
assert evaluated(obj)[1] == original_faces
set_value("Valid Only", False)
set_value("Depth Mask", False)
for background in (100, 250):
set_value("Invalid Distance", background)
points, faces, _ = evaluated(obj)
@@ -132,14 +128,13 @@ def test_controls_smoothing_and_material_slot_inheritance():
obj, set_value = panorama()
group = obj.modifiers[0].node_group
inputs = {s.name: s for s in group.interface.items_tree if s.item_type == "SOCKET" and s.in_out == "INPUT"}
assert inputs["Validity Threshold"].parent.name == "Options"
assert inputs["Mask Threshold"].parent.name == "Options"
assert inputs["Invalid Distance"].parent.name == "Options"
assert inputs["Invalid Distance"].default_value == 100
assert inputs["Invalid Distance"].subtype == "DISTANCE"
assert inputs["Smooth Weight"].parent.name == "Options"
assert inputs["Valid Only"].parent == inputs["Subdivide"].parent
assert inputs["Valid Only"].default_value
assert "Material" not in inputs
assert inputs["Depth Mask"].parent == inputs["Subdivide"].parent
assert inputs["Depth Mask"].default_value
obj.data.from_pydata([(0, 0, 0)], [], [])
material = bpy.data.materials.new("Slot Material")
obj.data.materials.append(material)
@@ -193,7 +188,7 @@ def test_split_strength_and_distance_scale_invariance():
obj, set_value = panorama(depth * scale)
counts = []
for split in (0, 0.1, 0.5, 1):
set_value("Split Threshold", split)
set_value("Depth Split", split)
points, faces, uv = evaluated(obj)
assert np.isfinite(points).all() and np.isfinite(uv).all()
edges = Counter(tuple(sorted((a, b))) for f in faces for a, b in zip(f, (*f[1:], f[0])))
@@ -209,7 +204,7 @@ def test_continuous_spherical_slope_does_not_split_at_seam_or_poles():
depth = 3 + 0.3 * np.cos(2 * np.pi * u) * np.sin(np.pi * v)
obj, set_value = panorama(depth)
original = evaluated(obj)
set_value("Split Threshold", 1)
set_value("Depth Split", 1)
set_value("Boundary Smooth", 12)
points, faces, uv = evaluated(obj)
np.testing.assert_allclose(points, original[0], atol=1e-6)
@@ -226,7 +221,7 @@ def test_split_corners_keep_their_rays_and_own_face_distance():
original, _, _ = evaluated(obj)
original_radii = np.linalg.norm(original, axis=1)
original_rays = original / original_radii[:, None]
set_value("Split Threshold", 1)
set_value("Depth Split", 1)
points, _, uv = evaluated(obj)
radii = np.linalg.norm(points, axis=1)
distance, indices = cKDTree(original_rays).query(points / radii[:, None])
@@ -245,7 +240,7 @@ def test_zero_split_bypasses_single_snapshot_strip_cleanup(monkeypatch):
with monkeypatch.context() as patch:
patch.setattr(depth_surface, "_remove_strip_faces", lambda group, geometry, **kw: geometry)
obj, set_value = panorama(depth)
set_value("Split Threshold", 1)
set_value("Depth Split", 1)
_, faces, _ = evaluated(obj)
edges = Counter(tuple(sorted((a, b))) for f in faces for a, b in zip(f, (*f[1:], f[0])))
strips = 0
@@ -255,7 +250,7 @@ def test_zero_split_bypasses_single_snapshot_strip_cleanup(monkeypatch):
assert strips > 0
obj, set_value = panorama(depth)
original_faces = evaluated(obj)[1]
set_value("Split Threshold", 1)
set_value("Depth Split", 1)
assert len(evaluated(obj)[1]) == len(faces) - strips
set_value("Split Threshold", 0)
set_value("Depth Split", 0)
assert evaluated(obj)[1] == original_faces