I’m making a simple server to recive OSC messages inside blender. The server itself is working fine, but I would like to do some cleanup before closing the script.
Which is the best way to do it?
I’ve tried catching a KeyboardInterrupt exception but I can’t manage to make it work, even if when I send the interrupt from the terminal it says:
Sent an internal break event. Press ^C again to kill Blender
If press ^C again it kills blender, freeing the used socket…which is fine for now, But in the future I would like to run the script multiple times the same Blender session.
A really good solution would be to stop it from the GUI, is there a way to do it?
The script is:
"""Simple OSC server
This program listens to one address, and prints some information about
received packets.
"""
import math
import time, threading
from pythonosc import dispatcher
from pythonosc import osc_server
def print_recibed_msg(addr, args, *osc):
"""Print the recibed OSC message."""
print("[{0}] - {1} {2}".format(addr, len(osc), args[0]))
for i in range(len(osc)):
print(osc[i])
#create the dispatcher
dispatcher = dispatcher.Dispatcher()
#register a handler to print on the address /print
dispatcher.map("/print", print_recibed_msg, "MSG")
#create and start the server
server = osc_server.OSCUDPServer(("127.0.0.1", 7000), dispatcher)
st = threading.Thread( target = server.serve_forever)
print("Serving on {}".format(server.server_address))
st.start()
try :
while 1 :
time.sleep(5)
except KeyboardInterrupt :
print ("
Closing OSCServer.")
server.shutdown()
print ("Waiting for Server-thread to finish")
st.join()
print ("Done")
Thank you erdinc for the answer.
I got it working with the modal timer, I paste the code below in case someone finds it useful:
After running the script, the operator can be invoked from the spacebar menu, with the name “receive OSC”
"""
Transform incoming osc messages to an object rotation
To be run from the spacebar menu, with the name of "Recive OSC"
To stop the reception and go back to normal blender use, press ESC or RMB
It expect to be an scene called 'Scene' with an object called 'Cube' (the default scene works fine)
Creates an UDP server at 127.0.0.1:7000 and attach a handler to the OSC address /quats
"""
import math
import time, threading
from pythonosc import dispatcher
from pythonosc import osc_server
import bpy
D = bpy.data
class OscOperator(bpy.types.Operator):
"""Recibe Osc messages and rotate an object"""
#Required properties of a Blender operator
bl_idname = "wm.receive_osc"
bl_label = "Receive OSC"
#Some vars to store stuff
_timer = None
theObject = 'Cube'
lastQuad = None
#Add a blender prop to the Scene class
bpy.types.Scene.custom = bpy.props.FloatVectorProperty(subtype='QUATERNION', name="elquat", size=4)
#Store the default scene's custom property inside a class member
quat = D.scenes['Scene'].custom
###########################################
### DEFINING bpy,types.Operator METHODS ###
###########################################
def invoke(self, context, event):
"""
The invoke() function is called when the operator is invoked by the user, for example from the spacebar menu
Right now we use it just to call execute()
"""
self.execute(context)
return {'RUNNING_MODAL'}
def execute(self, context):
"""
Execute() is called when the operator is executed from python
Here we create the timer, and append the modal handler
Then setup the object, and start the OSC server
"""
#Add the timer and modal handler to the window manager
wm = context.window_manager
self._timer = wm.event_timer_add(0.05, context.window)
wm.modal_handler_add(self)
#Set the object to quaternion rotate mode, and the initial rotation to the default
D.objects[self.theObject].rotation_mode = 'QUATERNION'
D.objects['Cube'].rotation_quaternion = (1,0,0,0)
#Start the server
print("************INIT SERVER***************")
self.startServer()
return {'RUNNING_MODAL'}
def modal(self, context, event):
"""
The modal function will be runned on every blender event (like a keyboard press, mousemove, click, etc).
In order to make it run at regular intervarls a timer is created inside invoke() which triggers a "TIMER" with a user defined delay.
To keep the modal running it should return {'RUNNING_MODAL'}, otherwise {'FINISHED'} or {'CANCELLED'}.
"""
# print("Event-type: ", event.type)
if event.type in {'RIGHTMOUSE', 'ESC'}:
self.cancel(context)
return {'CANCELLED'}
if event.type == 'TIMER':
# print(self.quat)
if self.lastQuad != self.quat:
self.lastQuad = self.quat.copy()
print(self.quat )
# D.objects[self.theObject].rotation_quaternion.rotate( self.quat )
D.objects[self.theObject].rotation_quaternion = self.quat
return {'RUNNING_MODAL'}
def cancel(self, context):
"""
cancel() get's called when the operator is about to exit
Here we remove the timer and stop the server
"""
wm = context.window_manager
wm.event_timer_remove(self._timer)
print ("
Closing OSCServer.")
self.server.shutdown()
print ("Waiting for Server-thread to finish")
self.st.join() ##!!!
print ("Done")
###########################################
### DEFINING CUSTOM METHODS ###
###########################################
def startServer(self):
"""
Start the OSC server on different thread and attach a handler to the OSC address "/quats"
"""
#create the dispatcher
self.dispatcher = dispatcher.Dispatcher()
#register a handler to set the recived values on the address /quats
#to a blender property somewhere
self.dispatcher.map("/quats", osc_to_prop_quat, self.quat)
#Start evetything
self.server = osc_server.OSCUDPServer(("10.42.0.1", 7000), self.dispatcher)
self.st = threading.Thread( target = self.server.serve_forever)
print("Serving on {}".format(self.server.server_address))
self.st.start()
###########################################
### OSC HANDLERS ###
###########################################
def osc_to_prop_quat(addr, obj, *val):
"""
OSC HANDLER get a tuple of 4 OSC values and set them the passed obj
"""
print("RECEIVED ", val)
if 4 == len(val):
for i in range(4):
obj[0][i] = float(val[i])
bpy.utils.register_class(OscOperator)