So a while back I wanted to create a background thread that checks the user selection at any given point. Now I know I could have done this with a script job but I wanted more flexibility so started looking into threading.
Now threading in maya is somewhat a nightmare as threads are not safe so if a thread crashes so does maya.
But I think I’ve come up with a “somewhat” stable version of threading. I am kind of cheating the function to run on the main thread. This way most Pymel functions will still work correctly without erroring out.
Now be careful though: It can still be unstable especially when using print statements.
The problem is that if one thread gets delayed and the other one starts that there is a slim chance that two or more threads try to do the same operation in maya at the same time. So for example they will both try to move object.vtx[1] to different positions or print to the script editor at the same time it will result in a crash as maya does not know what to do with the situation.
The class here calls the provided function every 1 second. unless specified otherwise. The timer will never be faster than the function takes. so if the function takes 3 seconds to complete then the interval will be 3 seconds even though you might set it to 0.001
It also supports string and mel functions.
Here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
##################################################### ## ## Author: Jan Pijpers ## ## Date : 10/04/2014 ## ## Module description: Threading module that allows user to create a sepperate thread and run their own funcitons. ## ## ##################################################### import threading import traceback import math import time from pymel import core as pm from maya import mel from maya import cmds import maya.utils ## Did not create a class that inherits from a threaded function as maya does not like it ## Mainly because threads are not thread safe in maya class threadedLoop( object ): def __init__( self, stopEvent, function, args = [], kwargs = {}, interval = 1.0, threadName = None, mel = False): ''' Description: loops a certain function at the specified interval. The function will never be called faster than it can run. so if the function takes 5 seconds setting the interval to less then 5 will not do anything. Also its a recursive function. So python might hit its recursion limit. if it does just increase the limit. Args: stopEvent : threading.event function : the function as is or as string args: arguments that need to be passed into the function kwargs: keyword arguments needed to be passed into the function interval : the interval at which the function needs to be called. (i recommend to not go lower than 0.001) threadName : obsolete for now. mel : is it a mel function ? make sure you set this if it is else you might get some funky list of errors. Note calling a mel function is a lot more unstable Returns: None Raises: Exception : catch all as I do not know what can go wrong in other peoples scripts. ''' ## Store variables self.stopEvent = stopEvent self.func = function self.args = args self.kwargs = kwargs self.thread = None self.mel = mel ## Check if the function is a string or not self.stringFunction = False if not mel and not type( self.func ) == type( '' ) else True self.interval = interval ## Create the timer thread . ( for some wierd reason more stable than creating a thread normally) ## This is the most volitile bit in maya hence only doing it once and then just r ## Get the function name self.funcName = self.func.__name__ if not self.stringFunction else self.func ## get the thread name (not used at the moment) self.threadName = self.funcName if not threadName else self.func.__name__ if not stringFunction else self.func def start( self ): ## Check if the thread allready exists if self.thread: self.stopEvent.clear() ## Just call our execute funtion self.execute( ) return self.thread = threading.Timer(self.interval, self.execute , [], {} ) ## Start the thread ## It will call the function once after the interval has finished (in the execute function it be called again. ) self.thread.start() def stop( self ): ## Set the stop signal. self.stopEvent.set() #self.thread.cancel() def execute( self ): ## All wrapped in a try try: ## Create the string for the arguments strArgs = ','.join( ["'" +str(a)+"'" for a in self.args]) if self.args else '' ## Create the string for the keyword arguments strKwargs = ','.join( [str(k) +'=' +str(v) for k,v in self.kwargs.iteritems()]) if self.kwargs else '' ## the sepperator and if we need it sepperator = ',' if strArgs and strKwargs else '' ## Create the function string so we can pass it into an eval deferred funcString = self.funcName +"("+strArgs+sepperator+strKwargs+")" ## If its a mel function we need to use te mel.eval if self.mel: cmds.evalDeferred( "from maya import mel;mel.eval('%s')"%funcString); else: ## If not we can just use an normal eval defered. Using cmds as its more stable then pymel. cmds.evalDeferred( funcString ) except Exception, e: ## Catch whatever exception happens. (does not always work because of the eval deferred ) #print e print traceback.format_exc() print "\nThreading stopped something went wrong", return ## Wait the prefered interval time.sleep(self.interval) ## Tell maya to do whatever it needs to maya.utils.processIdleEvents() ## check if the user wanted to stop the thread if self.stopEvent.isSet(): #self.stop() #self.thread = None return ## Call our start again. cmds.evalDeferred( self.start ) #testT.stopEvent = True #testT.thread.cancel() |
The evalDeferred is the most important part of this class. Without it there is a very high chance that maya will crash as it will most likely try to do two or more operations at the same time.
To see this in action with a funky little example copy this code into a file named:
threadedFunctions.py
into your scripts directory. i.e. C:\Users\<username>\Documents\maya\<maya version>\scripts
Now in maya create a poly plane of 100 units wide and high. (make sure you leave the name as default i.e.: “pPlane1”)
and give it 10 subdivisions in each direction.
and run this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import threadedFunctions reload( threadedFunctions ) import threading import math i = 0 def moveUP(): global i i+= 1 noVtx = len(pm.PyNode("pPlane1").vtx) row = math.sqrt (noVtx ) for x in xrange(noVtx): sinI = math.sin( x % row + (i*0.2) ) * 10 #pos = pm.PyNode("pPlane1.vtx[%s]" %str(x)).getPosition() pos = cmds.xform( "pPlane1.vtx[%s]" %str(x), query = True, translation = True) cmds.xform( "pPlane1.vtx[%s]" %str(x), translation = [pos[0],sinI,pos[2]] ) stopEvent = threading.Event() flagThread = threadedFunctions.threadedLoop( stopEvent, moveUP, interval = 0.1) flagThread.start() |
Now if you are lucky maya dint crash and you will see a flag like motion without the timeline advancing.
VERY IMPORTANT NOTE!!!. if you want to stop the thread run the following code.
1 2 3 4 |
## Run these lines to stop the thread flagThread.stop() ## OR set the thread event. stopEvent.set() |
DO NOT REDEFINE THE “flagThread” variable as that will lose your chance to kill the thread.
I hope this is of some use to someone. I guess you could even make a simple game with this in maya haha :D.