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_mesh_operators_and_tools_are_registered(self): from anyimage.operators.convert_to_panorama import ConvertToPanorama, GeneratePanorama self.assertIn(ConvertToPanorama, self.anyimage.CLASSES) self.assertIn(GeneratePanorama, self.anyimage.CLASSES) for generator in (GeneratePanorama, self.convert_to_plane.GenerateDepthPlane): self.assertIn(generator, self.anyimage.CLASSES) for name in ("source_object_name", "source_identity", "image_identity"): self.assertIn(name, generator.__annotations__) self.assertIn("HIDDEN", generator.__annotations__[name]["options"]) self.assertLess(self.anyimage.CLASSES.index(GeneratePanorama), self.anyimage.CLASSES.index(ConvertToPanorama)) 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.assertIn(self.cutout_main.CutoutSelectionToShape, self.anyimage.CLASSES) self.assertEqual(operator.bl_idname, "anyimage.convert_to_plane") 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, ) 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.assertEqual(mask_tool.bl_icon, "ops.sculpt.lasso_mask") 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.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.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.assertEqual(cutout_tool.bl_keymap[0][1]["value"], "PRESS") self.assertEqual( [entry[0] for entry in cutout_tool.bl_keymap[1:]], ["view3d.select"], ) self.assertEqual( cutout_tool.bl_keymap[1][2], {"properties": [("toggle", True)]}, ) cutout_module = sys.modules[cutout_tool.__module__] 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_gesture", "cutout_mesh_detail", "cutout_fine_outline", "cutout_alpha_threshold", "cutout_generate_normal", ], ) 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"], "SET") self.assertEqual(operator.__annotations__["radius"]["default"], 25) settings = self.anyimage.properties.AnyImageSettings.__annotations__ self.assertEqual(settings["mask_gesture"]["default"], "LASSO") self.assertEqual(settings["cutout_gesture"]["default"], "LASSO") self.assertEqual( [item[0] for item in settings["cutout_gesture"]["items"]], ["LASSO", "POLYLINE"], ) self.assertEqual( cutout_module.SelectCutoutSelection.__annotations__["gesture"]["default"], "LASSO", ) self.assertEqual( [(item[0], item[1], item[4]) for item in settings["mask_mode"]["items"]], [("SET", "Set", 0), ("EXTEND", "Extend", 1), ("SUBTRACT", "Subtract", 2)], ) self.assertEqual(settings["mask_mode"]["default"], "SET") 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_mesh_detail"]["name"], "Mesh Detail" ) self.assertEqual( settings["cutout_generate_normal"]["name"], "Normal Map", ) def test_rectify_tool_registration_and_properties(self): operator = self.rectify.RectifyImagePerspective tool = self.rectify.RectifyTool 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.assertEqual(tool.bl_keymap[0][1]["value"], "PRESS") self.assertEqual( tool.bl_keymap[1][1], {"type": "LEFTMOUSE", "value": "CLICK", "shift": True}, ) self.assertIn("aspect_ratio", 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) settings = self.anyimage.properties.AnyImageSettings.__annotations__ self.assertEqual(set(settings), { "cutout_gesture", "cutout_alpha_threshold", "cutout_mesh_detail", "cutout_fine_outline", "cutout_generate_normal", "mask_gesture", "mask_mode", "mask_radius", }) 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.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])