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:
@@ -1,5 +1,9 @@
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from types import MethodType
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from anyimage.common.selection import SelectionPath
|
||||
@@ -21,8 +25,10 @@ class Pie:
|
||||
class WindowManager:
|
||||
def __init__(self):
|
||||
self.pie = Pie()
|
||||
self.events = []
|
||||
|
||||
def popup_menu_pie(self, _event, draw, **_options):
|
||||
self.events.append(_event)
|
||||
menu = SimpleNamespace(layout=SimpleNamespace(menu_pie=lambda: self.pie))
|
||||
draw(menu, None)
|
||||
|
||||
@@ -31,14 +37,12 @@ def cutout_context(
|
||||
window_manager,
|
||||
mesh_detail="LOW",
|
||||
generate_normal=False,
|
||||
inward_axis="NEGATIVE_Z",
|
||||
alpha_threshold=0.9,
|
||||
fine_outline=True,
|
||||
):
|
||||
settings = SimpleNamespace(
|
||||
cutout_mesh_detail=mesh_detail,
|
||||
cutout_generate_normal=generate_normal,
|
||||
cutout_inward_axis=inward_axis,
|
||||
cutout_alpha_threshold=alpha_threshold,
|
||||
cutout_fine_outline=fine_outline,
|
||||
)
|
||||
@@ -94,7 +98,6 @@ def test_cutout_pie_uses_two_local_shapes_without_ai(monkeypatch):
|
||||
)
|
||||
assert not any(call[2].generate_normal for call in window_manager.pie.calls)
|
||||
assert all(call[2].point_spacing == 32 for call in window_manager.pie.calls)
|
||||
assert all(call[2].inward_axis == "NEGATIVE_Z" for call in window_manager.pie.calls)
|
||||
|
||||
|
||||
def test_cutout_pie_passes_image_lasso_options_to_all_ai_shapes(monkeypatch):
|
||||
@@ -103,7 +106,6 @@ def test_cutout_pie_passes_image_lasso_options_to_all_ai_shapes(monkeypatch):
|
||||
window_manager,
|
||||
mesh_detail="MEDIUM",
|
||||
generate_normal=True,
|
||||
inward_axis="POSITIVE_X",
|
||||
alpha_threshold=0.1,
|
||||
fine_outline=False,
|
||||
)
|
||||
@@ -149,7 +151,6 @@ def test_cutout_pie_passes_image_lasso_options_to_all_ai_shapes(monkeypatch):
|
||||
for call in window_manager.pie.calls
|
||||
)
|
||||
assert all(call[2].point_spacing == 16 for call in window_manager.pie.calls)
|
||||
assert all(call[2].inward_axis == "POSITIVE_X" for call in window_manager.pie.calls)
|
||||
|
||||
|
||||
def test_cutout_normal_space_is_derived_from_shape():
|
||||
@@ -175,20 +176,19 @@ def test_cutout_lasso_submission_passes_only_the_selected_path(monkeypatch):
|
||||
monkeypatch.setattr(cutout_main, "require_image_empty", lambda _name: source)
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: False)
|
||||
path = SelectionPath(points=((0, 0), (10, 0), (0, 10)))
|
||||
operator = SimpleNamespace(source_object_name=source.name, _pie_event=object())
|
||||
operator = SimpleNamespace(source_object_name=source.name, _pie_event=object(), gesture="LASSO")
|
||||
result = cutout_main.SelectCutoutSelection._submit_selection_path(operator, context, path)
|
||||
assert result == {"FINISHED"}
|
||||
assert SelectionPath.from_json(window_manager.pie.calls[0][2].selection_path_json) == path
|
||||
|
||||
|
||||
def test_cutout_tool_only_binds_lasso_drag_and_native_selection():
|
||||
def test_cutout_tool_starts_on_press_and_keeps_shift_selection():
|
||||
keymap = cutout_main.CutoutTool.bl_keymap
|
||||
assert keymap[0][1]["value"] == "CLICK_DRAG"
|
||||
assert [entry[0] for entry in keymap[1:]] == ["view3d.select", "view3d.select"]
|
||||
assert all(entry[1]["value"] != "DOUBLE_CLICK" for entry in keymap)
|
||||
assert "full_image" not in cutout_main.SelectCutoutSelection.__annotations__
|
||||
assert keymap[1][2] == {"properties": [("deselect_all", False)]}
|
||||
assert keymap[2][2] == {"properties": [("toggle", True)]}
|
||||
assert keymap[0][1]["value"] == "PRESS"
|
||||
assert len(keymap) == 2
|
||||
assert keymap[1][0] == "view3d.select"
|
||||
assert keymap[1][2] == {"properties": [("toggle", True)]}
|
||||
assert cutout_main.SelectCutoutSelection.resolve_on_press
|
||||
|
||||
|
||||
def test_cutout_f_has_no_option_shortcut(monkeypatch):
|
||||
@@ -209,7 +209,7 @@ def test_cutout_f_has_no_option_shortcut(monkeypatch):
|
||||
|
||||
def test_cutout_status_has_no_letter_shortcut():
|
||||
status = []
|
||||
operator = SimpleNamespace()
|
||||
operator = SimpleNamespace(gesture="LASSO")
|
||||
context = SimpleNamespace(
|
||||
workspace=SimpleNamespace(status_text_set=status.append)
|
||||
)
|
||||
@@ -220,3 +220,95 @@ def test_cutout_status_has_no_letter_shortcut():
|
||||
"Cutout Lasso: drag and release for shapes; RMB or Esc cancels"
|
||||
]
|
||||
assert "F" not in status[0]
|
||||
|
||||
|
||||
def test_cutout_menu_keeps_submitted_gesture_during_redraw(monkeypatch):
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(window_manager)
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: True)
|
||||
|
||||
def delayed_popup(_event, draw, **_options):
|
||||
context.scene.anyimage_settings.cutout_gesture = "LASSO"
|
||||
draw(SimpleNamespace(layout=SimpleNamespace(menu_pie=lambda: window_manager.pie)), None)
|
||||
|
||||
window_manager.popup_menu_pie = delayed_popup
|
||||
interaction.open_shape_pie(
|
||||
context, object(), cutout_source(),
|
||||
SelectionPath(points=((0, 0), (10, 0), (0, 10))), gesture="POLYLINE",
|
||||
)
|
||||
assert all(call[2].gesture == "POLYLINE" for call in window_manager.pie.calls)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("finish", ("START", "DOUBLE_CLICK", "RET", "NUMPAD_ENTER"))
|
||||
def test_cutout_polyline_edits_and_submits_through_shared_gesture(monkeypatch, finish):
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(window_manager)
|
||||
context.area = SimpleNamespace(tag_redraw=Mock())
|
||||
context.region = context.region_data = object()
|
||||
context.scene.anyimage_settings.cutout_gesture = "POLYLINE"
|
||||
source = cutout_source()
|
||||
monkeypatch.setattr(viewport, "active_view3d_tool_id", lambda _context: None)
|
||||
monkeypatch.setattr(viewport, "require_image_empty", lambda _name: source)
|
||||
monkeypatch.setattr(cutout_main, "require_image_empty", lambda _name: source)
|
||||
monkeypatch.setattr(viewport, "deserialize_matrix", lambda _data: None)
|
||||
monkeypatch.setattr(viewport, "image_empty_bounds", lambda _source: None)
|
||||
monkeypatch.setattr(viewport, "screen_path_to_image_pixels", lambda path, *_args: path)
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: False)
|
||||
operator = SimpleNamespace(
|
||||
source_object_name=source.name, source_matrix_data="matrix",
|
||||
active_tool_id="anyimage.cutout_lasso", _path=[(0, 0)],
|
||||
_polyline_confirm_type=None,
|
||||
_finish=Mock(),
|
||||
)
|
||||
for name in ("_modal_polyline", "_confirm_polyline", "_complete", "_submit_selection_path"):
|
||||
setattr(operator, name, MethodType(getattr(cutout_main.SelectCutoutSelection, name), operator))
|
||||
cutout_main.SelectCutoutSelection._read_gesture_settings(operator, context)
|
||||
|
||||
def event(kind, value="PRESS", point=(100, 100)):
|
||||
return cutout_main.SelectCutoutSelection.modal(operator, context, SimpleNamespace(
|
||||
type=kind, value=value, mouse_region_x=point[0], mouse_region_y=point[1],
|
||||
))
|
||||
|
||||
assert event("RET") == {"RUNNING_MODAL"}
|
||||
event("LEFTMOUSE", point=(100, 0))
|
||||
event("LEFTMOUSE", point=(100, 100))
|
||||
event("BACK_SPACE")
|
||||
assert operator._path == [(0, 0), (100, 0)]
|
||||
event("LEFTMOUSE", point=(100, 100))
|
||||
if finish == "START":
|
||||
result = event("LEFTMOUSE", point=(0, 0))
|
||||
elif finish == "DOUBLE_CLICK":
|
||||
result = event("LEFTMOUSE", "DOUBLE_CLICK")
|
||||
else:
|
||||
result = event(finish)
|
||||
assert result == {"RUNNING_MODAL"}
|
||||
assert window_manager.events == []
|
||||
operator._finish.assert_not_called()
|
||||
points = tuple(operator._path)
|
||||
assert event("MOUSEMOVE", point=(200, 200)) == {"RUNNING_MODAL"}
|
||||
assert event("BACK_SPACE") == {"RUNNING_MODAL"}
|
||||
assert tuple(operator._path) == points
|
||||
release_type = "LEFTMOUSE" if finish in {"START", "DOUBLE_CLICK"} else finish
|
||||
assert event("SPACE", "RELEASE") == {"RUNNING_MODAL"}
|
||||
result = event(release_type, "RELEASE")
|
||||
assert result == {"FINISHED"}
|
||||
assert len(window_manager.events) == 1
|
||||
assert window_manager.events[0].value == "RELEASE"
|
||||
operator._finish.assert_called_once_with(context)
|
||||
submitted = window_manager.pie.calls[0][2]
|
||||
assert submitted.gesture == "POLYLINE"
|
||||
assert SelectionPath.from_json(submitted.selection_path_json).points == ((0, 0), (100, 0), (100, 100))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("reason", ("ESC", "RIGHTMOUSE", "TOOL_SWITCH"))
|
||||
def test_cutout_polyline_cancel_cleans_preview(monkeypatch, reason):
|
||||
context = SimpleNamespace()
|
||||
operator = SimpleNamespace(
|
||||
gesture="POLYLINE", active_tool_id="anyimage.cutout_lasso", _finish=Mock(),
|
||||
_polyline_confirm_type="LEFTMOUSE",
|
||||
)
|
||||
operator._cancel_and_finish = MethodType(viewport.ImageGesture._cancel_and_finish, operator)
|
||||
monkeypatch.setattr(viewport, "active_view3d_tool_id", lambda _context: "other" if reason == "TOOL_SWITCH" else None)
|
||||
result = cutout_main.SelectCutoutSelection.modal(operator, context, SimpleNamespace(type=reason, value="PRESS"))
|
||||
assert result == {"CANCELLED"}
|
||||
operator._finish.assert_called_once_with(context)
|
||||
|
||||
Reference in New Issue
Block a user