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:
2026-09-12 19:20:06 +08:00
parent e8268504c2
commit 938d95db0f
175 changed files with 3812 additions and 6864 deletions

View File

@@ -17,7 +17,7 @@ class AddonRegistrationTest(BlenderTestCase):
self.assertFalse(name.startswith("anyimage_"), (operator, name))
def test_semantic_mesh_operators_are_registered_without_dialog_options(self):
def test_mesh_operators_and_tools_are_registered(self):
from anyimage.operators.convert_to_panorama import ConvertToPanorama, GeneratePanorama
self.assertIn(ConvertToPanorama, self.anyimage.CLASSES)
@@ -32,12 +32,8 @@ class AddonRegistrationTest(BlenderTestCase):
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)
@@ -80,14 +76,11 @@ class AddonRegistrationTest(BlenderTestCase):
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"],
@@ -102,7 +95,6 @@ class AddonRegistrationTest(BlenderTestCase):
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(
@@ -117,7 +109,6 @@ class AddonRegistrationTest(BlenderTestCase):
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",
@@ -151,25 +142,16 @@ class AddonRegistrationTest(BlenderTestCase):
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(cutout_tool.bl_keymap[0][1]["value"], "PRESS")
self.assertEqual(
[entry[0] for entry in cutout_tool.bl_keymap[1:]],
["view3d.select", "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__,
@@ -195,11 +177,11 @@ class AddonRegistrationTest(BlenderTestCase):
self.assertEqual(
drawn_settings,
[
"cutout_generate_normal",
"cutout_gesture",
"cutout_mesh_detail",
"cutout_fine_outline",
"cutout_alpha_threshold",
"cutout_inward_axis",
"cutout_generate_normal",
],
)
self.assertEqual(operator.__annotations__["gesture"]["default"], "LASSO")
@@ -211,11 +193,24 @@ class AddonRegistrationTest(BlenderTestCase):
("POLYLINE", "Polyline"),
],
)
self.assertEqual(operator.__annotations__["mode"]["default"], "SUBTRACT")
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["mask_mode"]["default"], "SUBTRACT")
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_")},
@@ -250,8 +245,6 @@ class AddonRegistrationTest(BlenderTestCase):
],
)
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"
)
@@ -259,34 +252,21 @@ class AddonRegistrationTest(BlenderTestCase):
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):
@@ -305,12 +285,11 @@ class AddonRegistrationTest(BlenderTestCase):
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",
"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()
@@ -356,7 +335,6 @@ class AddonRegistrationTest(BlenderTestCase):
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)
@@ -382,4 +360,3 @@ class AddonRegistrationTest(BlenderTestCase):
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"))