Add tests for image layer, image plane, inventory, saved layout, commands, documentation, manifest, and packaging
- Implement tests for image layer functionality including alpha curve and depth layer behavior. - Create tests for image plane evaluation and modifier input handling. - Add tests to verify the saved asset inventory and its persistence. - Introduce tests for the documentation command to ensure correct project version deployment. - Validate the extension manifest structure and required permissions. - Develop tests for the packaging process, ensuring correct platform handling and dependency management. - Enhance image depth plane group functionality with new split threshold input and surface processing.
This commit is contained in:
222
tests/anyimage/operators/test_cutout_interaction.py
Normal file
222
tests/anyimage/operators/test_cutout_interaction.py
Normal file
@@ -0,0 +1,222 @@
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
|
||||
from anyimage.common.selection import SelectionPath
|
||||
from anyimage.common import viewport
|
||||
from anyimage.operators.cutout_tool import interaction
|
||||
from anyimage.operators.cutout_tool import operators as cutout_main
|
||||
|
||||
|
||||
class Pie:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
|
||||
def operator(self, identifier, **options):
|
||||
operator = SimpleNamespace()
|
||||
self.calls.append((identifier, options, operator))
|
||||
return operator
|
||||
|
||||
|
||||
class WindowManager:
|
||||
def __init__(self):
|
||||
self.pie = Pie()
|
||||
|
||||
def popup_menu_pie(self, _event, draw, **_options):
|
||||
menu = SimpleNamespace(layout=SimpleNamespace(menu_pie=lambda: self.pie))
|
||||
draw(menu, None)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
return SimpleNamespace(
|
||||
window_manager=window_manager,
|
||||
scene=SimpleNamespace(anyimage_settings=settings),
|
||||
)
|
||||
|
||||
|
||||
def test_cutout_menu_captures_fine_outline_before_later_redraw(monkeypatch):
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(window_manager)
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: False)
|
||||
|
||||
def delayed_popup(_event, draw, **_options):
|
||||
context.scene.anyimage_settings.cutout_fine_outline = False
|
||||
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))))
|
||||
assert all(call[2].fine_outline is True for call in window_manager.pie.calls)
|
||||
|
||||
|
||||
def cutout_source(name="Source", size=(2048, 1024)):
|
||||
return SimpleNamespace(name=name, data=SimpleNamespace(size=size))
|
||||
|
||||
|
||||
def test_cutout_pie_uses_two_local_shapes_without_ai(monkeypatch):
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(window_manager, generate_normal=True)
|
||||
source = cutout_source()
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: False)
|
||||
|
||||
interaction.open_shape_pie(
|
||||
context,
|
||||
object(),
|
||||
source,
|
||||
SelectionPath(points=((0, 0), (10, 0), (0, 10))),
|
||||
)
|
||||
|
||||
assert [call[2].shape for call in window_manager.pie.calls] == [
|
||||
"FLAT",
|
||||
"SOLID",
|
||||
]
|
||||
assert [call[1]["text"] for call in window_manager.pie.calls] == [
|
||||
"Flat",
|
||||
"Solid",
|
||||
]
|
||||
assert all(
|
||||
call[0] == "anyimage.cutout_selection_to_shape"
|
||||
for call in window_manager.pie.calls
|
||||
)
|
||||
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):
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(
|
||||
window_manager,
|
||||
mesh_detail="MEDIUM",
|
||||
generate_normal=True,
|
||||
inward_axis="POSITIVE_X",
|
||||
alpha_threshold=0.1,
|
||||
fine_outline=False,
|
||||
)
|
||||
source = cutout_source(size=(4096, 2048))
|
||||
monkeypatch.setattr(interaction, "ai_ready", lambda: True)
|
||||
serialized = []
|
||||
original_to_json = SelectionPath.to_json
|
||||
|
||||
def record_to_json(selection_path):
|
||||
value = original_to_json(selection_path)
|
||||
serialized.append(value)
|
||||
return value
|
||||
|
||||
monkeypatch.setattr(SelectionPath, "to_json", record_to_json)
|
||||
|
||||
interaction.open_shape_pie(
|
||||
context,
|
||||
object(),
|
||||
source,
|
||||
SelectionPath(
|
||||
points=((0, 0), (10, 0), (0, 10)),
|
||||
),
|
||||
)
|
||||
|
||||
assert [call[2].shape for call in window_manager.pie.calls] == [
|
||||
"FLAT",
|
||||
"SOLID",
|
||||
"DEPTH_BALLOON",
|
||||
"DEPTH_SOLID",
|
||||
]
|
||||
assert all(call[2].generate_normal for call in window_manager.pie.calls)
|
||||
assert all(call[2].alpha_threshold == 0.1 for call in window_manager.pie.calls)
|
||||
assert all(call[2].fine_outline is False for call in window_manager.pie.calls)
|
||||
assert len(serialized) == 1
|
||||
assert set(json.loads(serialized[0])) == {"points"}
|
||||
assert all(
|
||||
call[2].selection_path_json == serialized[0]
|
||||
for call in window_manager.pie.calls
|
||||
)
|
||||
assert all(
|
||||
SelectionPath.from_json(call[2].selection_path_json).points
|
||||
== ((0.0, 0.0), (10.0, 0.0), (0.0, 10.0))
|
||||
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():
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SOLID", False) == "OBJECT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_BALLOON", False) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("FLAT", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("SOLID", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_BALLOON", True) == "TANGENT"
|
||||
assert cutout_main.normal_mode_for_shape("DEPTH_SOLID", True) == "OBJECT"
|
||||
|
||||
|
||||
def test_cutout_mesh_detail_maps_to_denser_sampling():
|
||||
assert [
|
||||
interaction.cutout_mesh_detail_spacing(detail)
|
||||
for detail in ("LOW", "MEDIUM", "HIGH", "ULTRA")
|
||||
] == [32, 16, 8, 4]
|
||||
|
||||
|
||||
def test_cutout_lasso_submission_passes_only_the_selected_path(monkeypatch):
|
||||
source = cutout_source()
|
||||
window_manager = WindowManager()
|
||||
context = cutout_context(window_manager)
|
||||
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())
|
||||
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():
|
||||
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)]}
|
||||
|
||||
|
||||
def test_cutout_f_has_no_option_shortcut(monkeypatch):
|
||||
operator = SimpleNamespace(
|
||||
gesture="LASSO",
|
||||
active_tool_id="anyimage.cutout_lasso",
|
||||
)
|
||||
monkeypatch.setattr(viewport, "active_view3d_tool_id", lambda _context: None)
|
||||
|
||||
result = cutout_main.SelectCutoutSelection.modal(
|
||||
operator,
|
||||
SimpleNamespace(),
|
||||
SimpleNamespace(type="F", value="PRESS"),
|
||||
)
|
||||
|
||||
assert result == {"PASS_THROUGH"}
|
||||
|
||||
|
||||
def test_cutout_status_has_no_letter_shortcut():
|
||||
status = []
|
||||
operator = SimpleNamespace()
|
||||
context = SimpleNamespace(
|
||||
workspace=SimpleNamespace(status_text_set=status.append)
|
||||
)
|
||||
|
||||
cutout_main.SelectCutoutSelection._set_status(operator, context)
|
||||
|
||||
assert status == [
|
||||
"Cutout Lasso: drag and release for shapes; RMB or Esc cancels"
|
||||
]
|
||||
assert "F" not in status[0]
|
||||
Reference in New Issue
Block a user