Files
AnyImage/tests/anyimage/test_addon_registration.py
nan 1622afa7a2 Enhance image editing tests and functionality
- Added parameterized tests for image edit replacement to ensure alpha mode preservation.
- Refactored existing tests to utilize new image editing methods and improve clarity.
- Introduced new tests for image target functionality, ensuring proper handling of shared textures and context.
- Updated various tests to validate alpha mode handling across different scenarios.
- Improved undo/redo functionality in image operators to maintain state consistency.
- Added support for texture node actions, enhancing integration with Blender's node system.
- Ensured that image properties are preserved during operations and that invalid contexts are handled gracefully.
2026-09-10 00:00:13 +08:00

376 lines
16 KiB
Python

import importlib
import sys
from types import SimpleNamespace
from unittest.mock import patch
from tests.support.blender import BlenderTestCase
class AddonRegistrationTest(BlenderTestCase):
def test_import_does_not_add_addon_directories_to_sys_path(self):
self.assertEqual(self.paths_after_import, self.paths_before_import)
def test_operator_properties_use_plain_names(self):
operators = self.anyimage.operators
for operator in operators.CLASSES:
for name in getattr(operator, "__annotations__", {}):
self.assertFalse(name.startswith("anyimage_"), (operator, name))
def test_semantic_mesh_operators_are_registered_without_dialog_options(self):
operator = self.convert_to_plane.ConvertToPlane
self.assertIn(operator, self.anyimage.CLASSES)
self.assertIn(self.convert_to_plane.ConvertToDepthPlane, self.anyimage.CLASSES)
self.assertIn(self.convert_to_plane.ConvertToReliefPlane, self.anyimage.CLASSES)
self.assertFalse(hasattr(self.convert_to_plane, "ConvertToDepthSurface"))
self.assertIn(self.cutout_main.CutoutSelectionToShape, self.anyimage.CLASSES)
self.assertEqual(operator.bl_idname, "anyimage.convert_to_plane")
self.assertNotIn("shadeless", operator.__annotations__)
self.assertNotIn("invoke", operator.__dict__)
self.assertNotIn("draw", operator.__dict__)
self.assertIn("REGISTER", operator.bl_options)
self.assertIn("UNDO", operator.bl_options)
self.assertEqual(operator.__annotations__["mesh_detail"]["default"], 0)
self.assertEqual(
self.convert_to_plane.ConvertToDepthPlane.__annotations__["mesh_detail"][
"default"
],
6,
)
self.assertEqual(
self.convert_to_plane.GenerateDepthPlane.__annotations__["mesh_detail"][
"default"
],
6,
)
image_empty = SimpleNamespace(
type="EMPTY",
empty_display_type="IMAGE",
data=object(),
)
image_plane = SimpleNamespace(
type="MESH",
modifiers=[
SimpleNamespace(node_group=SimpleNamespace(name="O Image Plane"))
],
)
plain_mesh = SimpleNamespace(type="MESH", modifiers=[])
self.assertTrue(operator.poll(SimpleNamespace(object=image_empty)))
self.assertFalse(operator.poll(SimpleNamespace(object=image_plane)))
self.assertFalse(operator.poll(SimpleNamespace(object=plain_mesh)))
def test_image_edit_tools_and_operators_are_registered(self):
operator = self.edit_mask.EditImageAlpha
frame_operator = self.image_frame.FrameImages
self.assertIn(operator, self.anyimage.CLASSES)
self.assertIn(frame_operator, self.anyimage.CLASSES)
self.assertIn(
self.rectify.RectifyImagePerspective,
self.anyimage.CLASSES,
)
self.assertNotIn("selection_path_json", operator.__annotations__)
mask_tool = self.edit_mask.MaskTool
self.assertNotIn(mask_tool, self.anyimage.CLASSES)
self.assertEqual(mask_tool.bl_operator, operator.bl_idname)
self.assertEqual(operator.bl_options, {"UNDO"})
self.assertNotIn("draw", operator.__dict__)
self.assertEqual(mask_tool.bl_icon, "ops.sculpt.lasso_mask")
self.assertNotIn("bl_options", mask_tool.__dict__)
self.assertEqual(mask_tool.bl_cursor, "PAINT_CROSS")
self.assertEqual(
mask_tool.bl_keymap[0][1]["value"],
"PRESS",
)
self.assertEqual(
mask_tool.bl_keymap[1][1],
{"type": "LEFTMOUSE", "value": "CLICK", "shift": True},
)
frame_tool = self.image_frame.FrameTool
self.assertNotIn(frame_tool, self.anyimage.CLASSES)
self.assertEqual(frame_tool.bl_operator, frame_operator.bl_idname)
self.assertEqual(frame_tool.bl_icon, "ops.sculpt.border_mask")
self.assertEqual(frame_operator.bl_options, {"UNDO"})
self.assertNotIn("bl_options", frame_tool.__dict__)
self.assertEqual(frame_tool.bl_keymap[0][1]["value"], "CLICK_DRAG")
self.assertIsNone(frame_tool.bl_keymap[0][2])
self.assertEqual(
[entry[0] for entry in frame_tool.bl_keymap[1:]],
["view3d.select", "view3d.select"],
)
self.assertEqual(
frame_tool.bl_keymap[1][2],
{"properties": [("deselect_all", True)]},
)
self.assertEqual(
frame_tool.bl_keymap[2][2],
{"properties": [("toggle", True)]},
)
self.assertNotIn("draw_settings", frame_tool.__dict__)
self.assertIn("draw_settings", mask_tool.__dict__)
settings_value = SimpleNamespace(
mask_gesture="LASSO",
mask_mode="SET",
mask_radius=25,
)
context = SimpleNamespace(
scene=SimpleNamespace(anyimage_settings=settings_value)
)
drawn_settings = []
layout = SimpleNamespace(
prop=lambda _settings, name, **_options: drawn_settings.append(name)
)
mask_tool.draw_settings(context, layout, None)
self.assertEqual(drawn_settings, ["mask_gesture", "mask_mode"])
settings_value.mask_gesture = "BRUSH"
drawn_settings.clear()
mask_tool.draw_settings(context, layout, None)
self.assertEqual(
drawn_settings,
["mask_gesture", "mask_mode", "mask_radius"],
)
settings_value.mask_gesture = "POLYLINE"
drawn_settings.clear()
mask_tool.draw_settings(context, layout, None)
self.assertEqual(drawn_settings, ["mask_gesture", "mask_mode"])
cutout_tool = importlib.import_module(
"anyimage.operators.cutout_tool"
).CutoutTool
self.assertEqual(
cutout_tool.bl_icon,
"ops.mesh.primitive_sphere_add_gizmo",
)
self.assertNotIn("bl_options", cutout_tool.__dict__)
self.assertEqual(cutout_tool.bl_keymap[0][1]["value"], "CLICK_DRAG")
self.assertEqual(
[entry[0] for entry in cutout_tool.bl_keymap[1:]],
["view3d.select", "view3d.select"],
)
self.assertEqual(
cutout_tool.bl_keymap[1][2],
{"properties": [("deselect_all", False)]},
)
self.assertEqual(
cutout_tool.bl_keymap[2][2],
{"properties": [("toggle", True)]},
)
cutout_module = sys.modules[cutout_tool.__module__]
self.assertNotIn(
"selection_path_json",
cutout_module.SelectCutoutSelection.__annotations__,
)
self.assertIn(
"selection_path_json",
cutout_module.CutoutSelectionToShape.__annotations__,
)
drawn_settings.clear()
with (
patch.object(
cutout_module,
"draw_ai_setup",
return_value={"ready": True},
) as setup,
patch.object(
cutout_module,
"draw_ai_property",
side_effect=lambda _layout, _settings, name, _status: (
drawn_settings.append(name)
),
) as draw,
):
cutout_tool.draw_settings(context, layout, None)
setup.assert_called_once_with(layout)
self.assertEqual(draw.call_count, 1)
self.assertEqual(
drawn_settings,
[
"cutout_generate_normal",
"cutout_mesh_detail",
"cutout_fine_outline",
"cutout_alpha_threshold",
"cutout_inward_axis",
],
)
self.assertEqual(operator.__annotations__["gesture"]["default"], "LASSO")
self.assertEqual(
[item[:2] for item in operator.__annotations__["gesture"]["items"]],
[
("LASSO", "Lasso"),
("BRUSH", "Brush"),
("POLYLINE", "Polyline"),
],
)
self.assertEqual(operator.__annotations__["mode"]["default"], "SUBTRACT")
self.assertEqual(operator.__annotations__["radius"]["default"], 25)
settings = self.anyimage.properties.AnyImageSettings.__annotations__
self.assertEqual(settings["mask_gesture"]["default"], "LASSO")
self.assertEqual(settings["mask_mode"]["default"], "SUBTRACT")
self.assertEqual(settings["mask_radius"]["default"], 25)
self.assertEqual(
{name for name in settings if name.startswith("mask_")},
{"mask_gesture", "mask_mode", "mask_radius"},
)
self.assertEqual(
set(operator.__annotations__),
{
"source_object_name",
"source_matrix_data",
"gesture",
"mode",
"radius",
},
)
self.assertEqual(settings["cutout_mesh_detail"]["default"], "LOW")
self.assertFalse(settings["cutout_fine_outline"]["default"])
self.assertFalse(cutout_module.CutoutSelectionToShape.__annotations__["fine_outline"]["default"])
self.assertEqual(settings["cutout_alpha_threshold"]["default"], 0.9)
self.assertEqual(settings["cutout_alpha_threshold"]["min"], 0.0)
self.assertEqual(settings["cutout_alpha_threshold"]["max"], 1.0)
self.assertEqual(settings["cutout_alpha_threshold"]["soft_min"], 0.1)
self.assertEqual(settings["cutout_alpha_threshold"]["soft_max"], 0.9)
self.assertEqual(settings["cutout_alpha_threshold"]["subtype"], "FACTOR")
self.assertEqual(
[item[:2] for item in settings["cutout_mesh_detail"]["items"]],
[
("LOW", "Low"),
("MEDIUM", "Medium"),
("HIGH", "High"),
("ULTRA", "Ultra"),
],
)
self.assertFalse(settings["cutout_generate_normal"]["default"])
self.assertEqual(settings["cutout_inward_axis"]["default"], "NEGATIVE_Z")
self.assertFalse(any("crop" in name for name in settings))
self.assertEqual(
settings["cutout_mesh_detail"]["name"], "Mesh Detail"
)
self.assertEqual(
settings["cutout_generate_normal"]["name"],
"Normal Map",
)
self.assertNotIn("image_extract_continuous", settings)
self.assertNotIn("image_extract_padding", settings)
self.assertNotIn("image_extract_remove_background", settings)
def test_rectify_tool_registration_and_properties(self):
operator = self.rectify.RectifyImagePerspective
tool = self.rectify.RectifyTool
settings = self.anyimage.properties.AnyImageSettings.__annotations__
self.assertIn(operator, self.anyimage.CLASSES)
self.assertNotIn(tool, self.anyimage.CLASSES)
self.assertEqual(tool.bl_operator, operator.bl_idname)
self.assertEqual(tool.bl_icon, "ops.sculpt.line_project")
self.assertNotIn("bl_options", tool.__dict__)
self.assertEqual(tool.bl_keymap[0][1]["value"], "PRESS")
self.assertEqual(
tool.bl_keymap[1][1],
{"type": "LEFTMOUSE", "value": "CLICK", "shift": True},
)
self.assertNotIn("bl_cursor", tool.__dict__)
self.assertNotIn("draw_settings", tool.__dict__)
self.assertNotIn("rectify_aspect_mode", settings)
self.assertNotIn("rectify_aspect_ratio", settings)
self.assertNotIn("aspect_mode", operator.__annotations__)
self.assertIn("aspect_ratio", operator.__annotations__)
self.assertNotIn("continuous", operator.__annotations__)
self.assertNotIn("padding", operator.__annotations__)
def test_registration_and_reverse_cleanup_preserve_other_addons(self):
self.assertIn(
self.anyimage.clipboard_image.PasteClipboardImage,
self.anyimage.CLASSES,
)
self.assertIn(
self.anyimage.clipboard_image.TrackNativeCopy,
self.anyimage.CLASSES,
)
existing_context_draw = object()
existing_outliner_draw = object()
existing_node_draw = object()
self.context_menu_draws.append(existing_context_draw)
self.outliner_menu_draws.append(existing_outliner_draw)
self.node_menu_draws.append(existing_node_draw)
self.assertFalse(any("debug" in cls.bl_idname.lower() for cls in self.anyimage.CLASSES if hasattr(cls, "bl_idname")))
settings = self.anyimage.properties.AnyImageSettings.__annotations__
self.assertEqual(set(settings), {
"cutout_alpha_threshold", "cutout_mesh_detail", "cutout_fine_outline",
"cutout_generate_normal", "cutout_inward_axis", "mask_gesture",
"mask_mode", "mask_radius", "video_frames",
})
self.anyimage.register()
system_classes = self.anyimage.runtime.operator_classes()
self.assertTrue(set(system_classes) <= set(self.registered))
self.assertEqual(
self.registered_tools,
[
(
self.image_frame.FrameTool,
{
"group": True,
"separator": True,
},
),
(
self.edit_mask.MaskTool,
{
"after": {self.image_frame.FrameTool.bl_idname},
"separator": False,
},
),
(
self.rectify.RectifyTool,
{
"after": {self.edit_mask.MaskTool.bl_idname},
"separator": False,
},
),
(
self.anyimage.CutoutTool,
{},
),
],
)
self.assertEqual(len(self.status_bar_draws), 1)
self.assertEqual(self.status_bar_draws[0]._owner, "anyimage")
self.assertIs(
self.context_menu_draws[0],
self.anyimage.draw_image_context_menu,
)
self.assertIs(
self.outliner_menu_draws[0],
self.anyimage.draw_image_context_menu,
)
self.assertFalse(hasattr(self.fake_bpy.types.Image, "anyimage_upscale_source"))
self.assertEqual(self.node_menu_draws, [self.anyimage.draw_texture_node_context_menu, existing_node_draw])
self.assertIn(self.anyimage.AnyImageTextureNodeMenu, self.registered)
unregister_order = []
original_unregister = self.fake_bpy.utils.unregister_class
def unregister(cls):
unregister_order.append(cls)
original_unregister(cls)
with patch.object(self.fake_bpy.utils, "unregister_class", side_effect=unregister):
self.anyimage.unregister()
self.assertEqual(
[cls for cls in unregister_order if cls in self.anyimage.CLASSES],
list(reversed(self.anyimage.CLASSES)),
)
self.assertEqual(self.registered, [])
self.assertEqual(self.registered_tools, [])
self.assertEqual(self.status_bar_draws, [])
self.assertEqual(self.context_menu_draws, [existing_context_draw])
self.assertEqual(self.outliner_menu_draws, [existing_outliner_draw])
self.assertEqual(self.node_menu_draws, [existing_node_draw])
self.anyimage.register()
self.assertEqual(self.node_menu_draws, [self.anyimage.draw_texture_node_context_menu, existing_node_draw])
self.anyimage.unregister()
self.assertEqual(self.node_menu_draws, [existing_node_draw])
self.assertFalse(hasattr(self.fake_bpy.types.Image, "anyimage_upscale_source"))