Skip to content

Commit

Permalink
Merge pull request #17 from sean1832/dev
Browse files Browse the repository at this point in the history
Implement Custom Handler for send operation
  • Loading branch information
sean1832 authored Sep 29, 2024
2 parents db642d7 + 1138c9a commit 45fb01d
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 66 deletions.
20 changes: 20 additions & 0 deletions portal/handlers/custom_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import bpy


class CustomHandler:
@staticmethod
def load(text_block_name, class_name, template_url=None) -> type:
text_block = bpy.data.texts.get(text_block_name)
if not text_block:
raise ImportError(f"Text block '{text_block_name}' not found")
module = {}
exec(text_block.as_string(), module)
if not module:
raise ImportError("Module not found.")
CustomHandler = module.get(class_name, None)
if not CustomHandler:
refer_to_template = (
"" if not template_url else f" Please refer to template ({template_url})."
)
raise ImportError(f"{class_name} class not found.{refer_to_template}")
return CustomHandler
21 changes: 7 additions & 14 deletions portal/handlers/string_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ..data_struct.camera import Camera
from ..data_struct.material import Material
from ..data_struct.mesh import Mesh
from ..utils.handler_loader import load_handler_from_text_block
from .custom_handler import CustomHandler


class StringHandler:
Expand All @@ -26,19 +26,12 @@ def handle_string(payload, data_type, uuid, channel_name, handler_src):

@staticmethod
def _handle_custom_data(payload, channel_name, uuid, handler_src):
if not handler_src:
raise ValueError("Handler source is empty.")
module = load_handler_from_text_block(handler_src)
if not module:
raise ImportError("Module not found.")
CustomHandler = module.get("CustomHandler", None)
if not CustomHandler:
raise ImportError(
"CustomHandler class not found. Please refer to template (https://github.com/sean1832/portal.blender/blob/main/templates/custom_data_handler.py)"
)

handler = CustomHandler()
handler.update(payload, channel_name, uuid)
custom_handler = CustomHandler.load(
handler_src,
"MyRecvHandler",
"https://github.com/sean1832/portal.blender/blob/main/templates/recv_handler.py",
)
handler = custom_handler(payload, channel_name, uuid)
handler.handle()

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion portal/ui/operators/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import bpy

from ..globals import CONNECTION_MANAGER, MODAL_OPERATORS
from ..utils.helper import is_connection_duplicated
from ..ui_utils.helper import is_connection_duplicated


# Operator to add new connection
Expand Down
49 changes: 35 additions & 14 deletions portal/ui/operators/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

import bpy

from ...handlers.custom_handler import CustomHandler
from ...handlers.string_handler import StringHandler
from ..globals import CONNECTION_MANAGER, MODAL_OPERATORS
from ..utils.helper import construct_packet_dict
from ..ui_utils.helper import construct_packet_dict


class ModalOperator(bpy.types.Operator):
Expand All @@ -20,14 +21,21 @@ def __init__(self):
self.render_complete_handler = None
self.frame_change_handler = None
self.scene_update_handler = None
self.custom_event_handler = None

def modal(self, context, event):
connection = self._get_connection(context)
if not connection or self._should_cancel(connection):
if not connection:
self.report({"ERROR"}, "Connection not found.")
return {"CANCELLED"}

server_manager = self._get_server_manager(connection)
if not server_manager:
self.report({"ERROR"}, "Server manager not found.")
return {"CANCELLED"}

if self._is_server_shutdown(server_manager):
self.report({"ERROR"}, "Server has been shut down.")
return {"CANCELLED"}

# Handle server errors and tracebacks
Expand All @@ -45,6 +53,7 @@ def modal(self, context, event):
def execute(self, context):
connection = self._get_connection(context)
if not connection:
self.report({"ERROR"}, "Connection not found.")
return {"CANCELLED"}

# Set up the timer and register modal handler
Expand Down Expand Up @@ -130,15 +139,10 @@ def _report_error(self, context, message, server_manager, connection, traceback=
self.cancel(context)
return {"CANCELLED"}

def _send_data_on_event(self, scene, connection_uuid):
connection = self._get_connection_by_uuid(connection_uuid)
if not connection or not connection.running:
return

def _send_data_on_event(self, scene, connection):
server_manager = self._get_server_manager(connection)
if not server_manager:
return

self._handle_send_event(bpy.context, connection, server_manager)

def _get_connection(self, context):
Expand All @@ -154,30 +158,43 @@ def _get_connection_by_uuid(self, uuid):
def _get_server_manager(self, connection):
return CONNECTION_MANAGER.get(connection.connection_type, self.uuid, connection.direction)

def _should_cancel(self, connection):
server_manager = self._get_server_manager(connection)
if server_manager and server_manager.is_shutdown():
def _is_server_shutdown(self, server_manager):
if server_manager.is_shutdown():
self.cancel(bpy.context)
return True
return False

def _register_event_handlers(self, connection):
if "RENDER_COMPLETE" in connection.event_types:
# https://docs.blender.org/api/current/bpy.app.handlers.html#bpy.app.handlers.render_complete
self.render_complete_handler = lambda scene: self._send_data_on_event(scene, self.uuid)
self.render_complete_handler = lambda scene: self._send_data_on_event(scene, connection)
bpy.app.handlers.render_complete.append(self.render_complete_handler)

if "FRAME_CHANGE" in connection.event_types:
# https://docs.blender.org/api/current/bpy.app.handlers.html#bpy.app.handlers.frame_change_post
self.frame_change_handler = lambda scene: self._send_data_on_event(scene, self.uuid)
self.frame_change_handler = lambda scene: self._send_data_on_event(scene, connection)
bpy.app.handlers.frame_change_post.append(self.frame_change_handler)

if "SCENE_UPDATE" in connection.event_types:
# on any scene event
# https://docs.blender.org/api/current/bpy.app.handlers.html#bpy.app.handlers.depsgraph_update_post
self.scene_update_handler = lambda scene: self._send_data_on_event(scene, self.uuid)
self.scene_update_handler = lambda scene: self._send_data_on_event(scene, connection)
bpy.app.handlers.depsgraph_update_post.append(self.scene_update_handler)

if "CUSTOM" in connection.event_types:
# Custom event
try:
handler = CustomHandler.load(
connection.custom_handler,
"MySendEventHandler",
"https://github.com/sean1832/portal.blender/blob/main/templates/sender_handler.py",
)
self.custom_event_handler = handler(self._get_server_manager(connection))
self.custom_event_handler.register()
except Exception as e:
self.report({"ERROR"}, f"Error loading custom event handler: {e}")
return {"CANCELLED"}

def _unregister_event_handlers(self):
if self.render_complete_handler:
bpy.app.handlers.render_complete.remove(self.render_complete_handler)
Expand All @@ -191,6 +208,10 @@ def _unregister_event_handlers(self):
bpy.app.handlers.depsgraph_update_post.remove(self.scene_update_handler)
self.scene_update_handler = None

if self.custom_event_handler:
self.custom_event_handler.unregister()
self.custom_event_handler = None


def register():
bpy.utils.register_class(ModalOperator)
Expand Down
14 changes: 7 additions & 7 deletions portal/ui/panels/server_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ def draw(self, context):
self._draw_custom_handler(sub_box, connection)
elif connection.event_types == "TIMER":
sub_box.prop(connection, "event_timer", text="Interval (sec)")

sub_box.prop(connection, "precision", text="Precision")

sub_box.separator()
sub_box.operator(
"portal.dict_item_editor", text="Data Editor", icon="MODIFIER_DATA"
).uuid = connection.uuid
if not connection.event_types == "CUSTOM":
sub_box.prop(connection, "precision", text="Precision")
sub_box.separator()
sub_box.operator(
"portal.dict_item_editor", text="Data Editor", icon="MODIFIER_DATA"
).uuid = connection.uuid

layout.operator("portal.add_connection", text="Add New Connection", icon="ADD")

Expand Down
3 changes: 1 addition & 2 deletions portal/ui/properties/connection_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ class PortalConnection(bpy.types.PropertyGroup):
("RENDER_COMPLETE", "Render Complete", "Trigger after rendering is complete"),
("FRAME_CHANGE", "Frame Change", "Trigger after frame change"),
("TIMER", "Timer", "Trigger on timer event (computational intensive!)"),
# ("CUSTOM", "Custom", "Trigger on custom event"),
# TODO: Implement custom event
("CUSTOM", "Custom", "Trigger on custom event"),
],
)
precision: bpy.props.FloatProperty(
Expand Down
File renamed without changes.
10 changes: 0 additions & 10 deletions portal/utils/handler_loader.py

This file was deleted.

18 changes: 0 additions & 18 deletions templates/custom_data_handler.py

This file was deleted.

17 changes: 17 additions & 0 deletions templates/recv_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Portal.blender: v0.2.0
# https://github.com/sean1832/portal.blender/blob/main/templates/recv_handler.py

# This is a template for handling custom data received.
# Attach this script to the 'Custom Handler' field in the 'Portal Server' panel
# after selecting 'Receive' as the direction and 'Custom' as the data type.

class MyRecvHandler:
def __init__(self, payload, channel_name, channel_uuid):
"""Constructor. (Do not modify this part)"""
self.data = payload
self.channel_name = channel_name
self.channel_uuid = channel_uuid

def handle(self) -> None:
"""Handle received message."""
print(f"Received custom data: {self.data} \nfrom channel: {self.channel_uuid}")
62 changes: 62 additions & 0 deletions templates/send_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Portal.blender: v0.2.0
# https://github.com/sean1832/portal.blender/blob/main/templates/send_handler.py

# This is a template for sending custom data on a specific event trigger.
# Attach this script to the 'Custom Handler' field in the 'Portal Server' panel
# after selecting 'Send' as the direction and 'Custom' as the event trigger type.

import bpy
import json
import uuid

class MySendEventHandler:
def __init__(self, server_manager):
"""Constructor. (Do not modify this part)"""
self.scene_update_handler = None
self.server_manager = server_manager

def _send_data_on_event(self, scene):
"""Send data when the event is triggered. (Do not modify this part)"""
message_to_send = self._construct_data()
if message_to_send:
self.server_manager.data_queue.put(message_to_send)

def _construct_data(self) -> str:
"""Construct the custom data to be sent. Modify this part with your custom logic."""
return json.dumps({
"Key": "Insert your custom data to send here. This is just a placeholder.",
"Nested": {
"Array": [1, 2, 3, 4, 5],
},
"UUID": str(uuid.uuid4())
})

def register(self):
"""Register the event handler. Update the trigger event as needed."""
self.scene_update_handler = lambda scene: self._send_data_on_event(scene)

# Modify this line to set your desired event trigger:
# see doc: https://docs.blender.org/api/current/bpy.app.handlers.html#bpy.app.handlers.depsgraph_update_post
bpy.app.handlers.depsgraph_update_post.append(self.scene_update_handler) # On scene update

# Other trigger event examples (uncomment if needed):
# ---------------------------
# bpy.app.handlers.render_init.append(self.scene_update_handler) # On render start
# bpy.app.handlers.render_complete.append(self.scene_update_handler) # On render completion
# bpy.app.handlers.render_write.append(self.scene_update_handler) # On render frame write
# bpy.app.handlers.save_post.append(self.scene_update_handler) # After saving the file

def unregister(self):
"""Unregister the event handler. Be sure to remove the specific event."""
if self.scene_update_handler:
# Remove the active event trigger:
bpy.app.handlers.depsgraph_update_post.remove(self.scene_update_handler)

# Remove other events if previously registered (uncomment if used):
# ---------------------------
# bpy.app.handlers.render_init.remove(self.scene_update_handler)
# bpy.app.handlers.render_complete.remove(self.scene_update_handler)
# bpy.app.handlers.render_write.remove(self.scene_update_handler)
# bpy.app.handlers.save_post.remove(self.scene_update_handler)

self.scene_update_handler = None

0 comments on commit 45fb01d

Please sign in to comment.