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

@@ -3,11 +3,50 @@ import importlib
import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from unittest.mock import Mock, patch
from tests.support.blender import BlenderTestCase
class CutoutOperatorsTest(BlenderTestCase):
def test_cutout_gestures_start_on_empty_pick_and_lasso_finishes_on_release(self):
viewport = importlib.import_module("anyimage.common.viewport")
source = SimpleNamespace(name="Source", matrix_world="matrix", data=object())
context = SimpleNamespace(
area=SimpleNamespace(as_pointer=lambda: 1, tag_redraw=Mock()),
region=SimpleNamespace(as_pointer=lambda: 2),
workspace=SimpleNamespace(status_text_set=Mock()),
window_manager=SimpleNamespace(modal_handler_add=Mock()),
scene=SimpleNamespace(anyimage_settings=SimpleNamespace(mask_gesture="BRUSH")),
)
event = SimpleNamespace(type="LEFTMOUSE", value="PRESS", mouse_region_x=12, mouse_region_y=18)
with (
patch.object(viewport, "resolve_image_edit_click", return_value=(source, True)),
patch.object(viewport, "is_animated_image", return_value=False),
patch.object(viewport, "serialize_matrix", return_value="matrix"),
patch.object(viewport, "active_view3d_tool_id", return_value=None),
patch.object(self.fake_bpy.types, "SpaceView3D", SimpleNamespace(draw_handler_add=Mock()), create=True),
):
for gesture in ("LASSO", "POLYLINE"):
with self.subTest(gesture=gesture):
context.scene.anyimage_settings.cutout_gesture = gesture
operator = self.cutout_main.SelectCutoutSelection()
operator.report = Mock()
self.assertEqual(operator.invoke(context, event), {"RUNNING_MODAL"})
self.assertEqual(operator._path, [(12.0, 18.0)])
self.assertEqual(operator.gesture, gesture)
self.assertEqual(context.scene.anyimage_settings.mask_gesture, "BRUSH")
operator._complete = Mock(return_value={"FINISHED"})
release = SimpleNamespace(type="LEFTMOUSE", value="RELEASE", mouse_region_x=20, mouse_region_y=30)
result = operator.modal(context, release)
if gesture == "LASSO":
self.assertEqual(result, {"FINISHED"})
operator._complete.assert_called_once_with(context)
else:
operator._complete.assert_not_called()
status = context.workspace.status_text_set.call_args.args[0]
self.assertIn("Polyline", status)
self.assertIn("Backspace", status)
def test_flat_and_solid_without_ai_build_locally(self):
import numpy as np
@@ -29,6 +68,7 @@ class CutoutOperatorsTest(BlenderTestCase):
{"points": [[1, 1], [9, 1], [9, 9], [1, 9]]}
),
shape=shape,
gesture="POLYLINE",
point_spacing=32,
alpha_threshold=0.25,
fine_outline=False,
@@ -43,7 +83,7 @@ class CutoutOperatorsTest(BlenderTestCase):
return_value=source,
),
patch.object(
self.cutout_main, "video_total_frames", return_value=1
self.cutout_main, "is_animated_image", return_value=False
),
patch.object(
self.cutout_main, "image_rgba", return_value=source_rgba
@@ -91,10 +131,10 @@ class CutoutOperatorsTest(BlenderTestCase):
32.0,
selection_mask.values,
selection_mask.bounds,
inward_axis="NEGATIVE_Z",
alpha_threshold=0.25,
fine_outline=False,
color_image="Color",
gesture="POLYLINE",
)
@@ -131,7 +171,7 @@ class CutoutOperatorsTest(BlenderTestCase):
"require_image_empty",
return_value=source,
),
patch.object(self.cutout_main, "video_total_frames", return_value=1),
patch.object(self.cutout_main, "is_animated_image", return_value=False),
patch.object(
self.cutout_main,
"image_rgba",
@@ -212,7 +252,7 @@ class CutoutOperatorsTest(BlenderTestCase):
"require_image_empty",
return_value=source,
),
patch.object(self.cutout_main, "video_total_frames", return_value=1),
patch.object(self.cutout_main, "is_animated_image", return_value=False),
patch.object(self.cutout_main, "image_rgba"),
patch.object(
self.cutout_main,
@@ -278,9 +318,6 @@ class CutoutOperatorsTest(BlenderTestCase):
self.assertTrue(parameters["generate_depth"])
self.assertEqual(parameters["alpha_threshold"], 0.25)
self.assertEqual(parameters["normal_mode"], "OBJECT")
self.assertNotIn("polygon", parameters)
self.assertNotIn("shape", parameters)
self.assertNotIn("normal_analysis", parameters)
def test_cutout_geometry_settings_use_configured_moge(self):
@@ -339,12 +376,12 @@ class CutoutOperatorsTest(BlenderTestCase):
self.assertEqual(
layout.events,
[
setup,
("prop", "cutout_generate_normal", False),
("prop", "cutout_gesture", True),
("prop", "cutout_mesh_detail", True),
("prop", "cutout_fine_outline", True),
("prop", "cutout_alpha_threshold", True),
("prop", "cutout_inward_axis", True),
setup,
("prop", "cutout_generate_normal", False),
],
)
@@ -356,7 +393,7 @@ class CutoutOperatorsTest(BlenderTestCase):
self.assertTrue(all(event[2] for event in layout.events))
def test_cutout_ai_response_reuses_bounds_color_and_lasso(self):
def test_cutout_ai_response_reuses_bounds_color_and_gesture_snapshot(self):
import numpy as np
source = SimpleNamespace(data=SimpleNamespace())
@@ -375,6 +412,7 @@ class CutoutOperatorsTest(BlenderTestCase):
operator.input_path = __file__
operator.color_path = __file__
operator.shape = "FLAT"
operator.gesture = "POLYLINE"
operator.point_spacing = 32
operator.generate_normal = True
operator._selection_mask = selection_mask
@@ -397,7 +435,10 @@ class CutoutOperatorsTest(BlenderTestCase):
) as color_result,
patch.object(self.cutout_main, "create_cutout_shape") as create_shape,
):
message = operator.response(SimpleNamespace(), result)
context = SimpleNamespace(scene=SimpleNamespace(
anyimage_settings=SimpleNamespace(cutout_gesture="LASSO")
))
message = operator.response(context, result)
color_result.assert_called_once_with(
__file__,
@@ -405,6 +446,7 @@ class CutoutOperatorsTest(BlenderTestCase):
)
self.assertEqual(message, "Flat is ready")
self.assertEqual(create_shape.call_args.kwargs["gesture"], "POLYLINE")
content_values = create_shape.call_args.args[4]
np.testing.assert_array_equal(content_values, pure_values)
self.assertEqual(create_shape.call_args.args[5], selection_mask.bounds)