- Replaced the Mesh Detail parameter with a new Edge Length property to define cutout sampling in physical units. - Introduced Minimum Relative Edge Length preference to control effective spacing based on the bounding box of the cutout content. - Updated tests to validate the new edge length functionality and ensure consistent behavior across different resolutions and canvas sizes. - Removed deprecated parameters and adjusted related properties to align with the new edge length implementation. - Enhanced geometry sampling logic to utilize effective world spacing for both standard and fine outline modes.
355 lines
14 KiB
Python
355 lines
14 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_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_edge_length",
|
|
"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_edge_length"]["default"], 0.1)
|
|
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(settings["cutout_edge_length"]["unit"], "LENGTH")
|
|
self.assertFalse(settings["cutout_generate_normal"]["default"])
|
|
self.assertEqual(
|
|
settings["cutout_edge_length"]["name"], "Edge Length"
|
|
)
|
|
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_edge_length", "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])
|