How to stop a script from terminal or GUI

Hi!

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")

…anyone?

Maybe you can create an operator. “Operator Modal” sample may help. It is in the templates menu in the text editor.

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)