feat(node_assets): add image plane geometry nodes and validation scripts
- Implemented `image_plane.py` to build the Image Plane Geometry Nodes group with customizable parameters for geometry, subdivide, and thickness. - Created `image_plane_validation.py` to validate the functionality of the image plane, ensuring correct geometry generation and material assignment. - Developed `material_layers.py` to construct shader node groups for image materials, including depth and normal handling. - Authored `plane_layouts.py` to define node arrangements for image geometry assets. - Introduced `shadeless.py` to create a shadeless shader node group that displays color without scene lighting while preserving shadows and reflections.
This commit is contained in:
151
tests/test_image_menu.py
Normal file
151
tests/test_image_menu.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
from blender_test_support import BlenderTestCase
|
||||
|
||||
|
||||
class ImageMenuTest(BlenderTestCase):
|
||||
def test_image_menu_keeps_ai_features_clickable_and_offers_models(self):
|
||||
calls = []
|
||||
labels = {}
|
||||
|
||||
class Layout:
|
||||
operator_context = ""
|
||||
enabled = True
|
||||
|
||||
def operator(self, identifier, **options):
|
||||
calls.append((identifier, self.enabled))
|
||||
labels[identifier] = options.get("text")
|
||||
return SimpleNamespace()
|
||||
|
||||
def separator(self):
|
||||
calls.append(("separator", self.enabled))
|
||||
|
||||
menu = self.anyimage.AnyImageImageMenu()
|
||||
menu.layout = Layout()
|
||||
with patch(
|
||||
"anyimage.properties.ai_status",
|
||||
return_value={
|
||||
"environment_ready": True,
|
||||
"missing_models": ("BEN2_BASE",),
|
||||
"ready": False,
|
||||
},
|
||||
):
|
||||
menu.draw(
|
||||
SimpleNamespace(
|
||||
object=SimpleNamespace(
|
||||
type="EMPTY",
|
||||
empty_display_type="IMAGE",
|
||||
data=SimpleNamespace(size=(640, 480)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
calls,
|
||||
[
|
||||
("anyimage.convert_to_plane", True),
|
||||
("anyimage.convert_to_depth_plane", True),
|
||||
("separator", True),
|
||||
("anyimage.remove_image_background", True),
|
||||
("anyimage.upscale_image", True),
|
||||
("separator", True),
|
||||
("anyimage.setup_ai_environment", True),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
labels["anyimage.setup_ai_environment"],
|
||||
"Download Required Models…",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
labels["anyimage.upscale_image"],
|
||||
"Upscale (1280 × 960)",
|
||||
)
|
||||
|
||||
|
||||
def test_image_menu_opens_environment_settings_when_ai_is_ready(self):
|
||||
calls = []
|
||||
|
||||
class Layout:
|
||||
operator_context = ""
|
||||
enabled = True
|
||||
|
||||
def operator(self, identifier, **_options):
|
||||
calls.append((identifier, self.enabled))
|
||||
return SimpleNamespace()
|
||||
|
||||
def separator(self):
|
||||
calls.append(("separator", self.enabled))
|
||||
|
||||
menu = self.anyimage.AnyImageImageMenu()
|
||||
menu.layout = Layout()
|
||||
with patch(
|
||||
"anyimage.properties.ai_status",
|
||||
return_value={
|
||||
"environment_ready": True,
|
||||
"missing_models": (),
|
||||
"ready": True,
|
||||
},
|
||||
):
|
||||
menu.draw(None)
|
||||
|
||||
self.assertIn(("anyimage.open_ai_environment_settings", True), calls)
|
||||
self.assertNotIn(("anyimage.setup_ai_environment", True), calls)
|
||||
|
||||
|
||||
def test_image_edit_activation_defaults_to_frame(self):
|
||||
calls = []
|
||||
previous_ops = getattr(self.fake_bpy, "ops", None)
|
||||
self.fake_bpy.ops = SimpleNamespace(
|
||||
wm=SimpleNamespace(
|
||||
tool_set_by_id=lambda **options: calls.append(options) or {"FINISHED"}
|
||||
)
|
||||
)
|
||||
try:
|
||||
result = self.image_frame.ActivateImageEditTool().execute(None)
|
||||
finally:
|
||||
if previous_ops is None:
|
||||
del self.fake_bpy.ops
|
||||
else:
|
||||
self.fake_bpy.ops = previous_ops
|
||||
|
||||
self.assertEqual(result, {"FINISHED"})
|
||||
self.assertEqual(
|
||||
calls,
|
||||
[{"name": "anyimage.frame", "space_type": "VIEW_3D"}],
|
||||
)
|
||||
|
||||
|
||||
def test_image_operators_stay_clickable_until_server_is_busy(self):
|
||||
context = SimpleNamespace(
|
||||
object=SimpleNamespace(data=SimpleNamespace()),
|
||||
)
|
||||
operators = (
|
||||
(self.convert_to_plane, self.convert_to_plane.ConvertToDepthPlane),
|
||||
(self.remove_background, self.remove_background.RemoveImageBackground),
|
||||
)
|
||||
|
||||
for busy, expected in ((False, True), (True, False)):
|
||||
with (
|
||||
self.subTest(busy=busy),
|
||||
patch.object(
|
||||
self.anyimage.runtime,
|
||||
"server_busy",
|
||||
return_value=busy,
|
||||
),
|
||||
):
|
||||
for module, operator in operators:
|
||||
with (
|
||||
self.subTest(operator=operator.__name__),
|
||||
patch.object(
|
||||
module,
|
||||
"is_image_empty",
|
||||
return_value=True,
|
||||
),
|
||||
patch.object(
|
||||
module,
|
||||
"video_total_frames",
|
||||
return_value=1,
|
||||
),
|
||||
):
|
||||
self.assertEqual(operator.poll(context), expected)
|
||||
Reference in New Issue
Block a user