Package mrv :: Package maya :: Module util
[hide private]
[frames] | no frames]

Source Code for Module mrv.maya.util

  1  # -*- coding: utf-8 -*- 
  2  """All kinds of utility methods and classes that are used in more than one modules """ 
  3  __docformat__ = "restructuredtext" 
  4   
  5  import maya.mel as mm 
  6  import maya.cmds as cmds 
  7  import maya.OpenMaya as api 
  8  import mrv.util as util 
  9  from mrv.util import capitalize,uncapitalize 
 10  import networkx.exception as networkxexc 
 11   
 12  import traceback 
 13   
 14  import weakref 
 15   
 16  __all__ = ("noneToList", "isIterable", "pythonToMel", "makeEditOrQueryMethod",  
 17             "queryMethod", "editMethod", "propertyQE", "Mel", "OptionVarDict",  
 18             "optionvars", "StandinClass", "MetaClassCreator", "CallbackEventBase",  
 19             "MEnumeration", "notifyException") 
20 21 #{ Utility Functions 22 -def noneToList( res ):
23 """:return: list instead of None""" 24 if res is None: 25 return list() 26 return res
27
28 -def isIterable( obj ):
29 return hasattr(obj,'__iter__') and not isinstance(obj,basestring)
30
31 -def pythonToMel(arg):
32 if isinstance(arg,basestring): 33 return u'"%s"' % cmds.encodeString(arg) 34 elif isIterable(arg): 35 return u'{%s}' % ','.join( map( pythonToMel, arg) ) 36 return unicode(arg)
37 #} END utility functions
38 39 40 #{ Decorator 41 42 -def _logException(func, reraise):
43 def wrapper(*args, **kwargs): 44 try: 45 return func(*args, **kwargs) 46 except Exception, e: 47 # print a full stack trace - for now not using the log 48 if reraise: 49 traceback.print_exc() 50 51 if api.MGlobal.mayaState() == api.MGlobal.kInteractive: 52 import mrv.maya.ui as ui 53 msg = str(e) 54 if reraise: 55 msg += "\n\nSee the script editor for details" 56 ui.ChoiceDialog( t=str(type(e)), 57 m=msg, 58 c=['Confirm'] ).choice() 59 # END show popup 60 if reraise: 61 raise
62 # END wrapper 63 wrapper.__name__ = func.__name__ 64 return wrapper 65
66 -def logException(func):
67 """Decorator which shows short exception information in a popup and a full 68 stack trace to stdout. Finally the exception will be reraised""" 69 return _logException(func, reraise=True)
70
71 -def notifyException(func):
72 """As logException, but does not reraise on error. This is useful if you just 73 want to see a popup, but not cause maya to remove the possibly problematic UI 74 callback. Additionally, no stacktrace will be shown""" 75 return _logException(func, reraise=False)
76
77 #} END decorator 78 79 #{ MEL Function Wrappers 80 81 -def makeEditOrQueryMethod( inCmd, flag, isEdit=False, methodName=None ):
82 """Create a function calling inFunc with an edit or query flag set. 83 84 :note: THIS CODE HAS BEEN DUPLICATED TO `mrv.maya.ui.util` ! 85 :param inCmd: maya command to call 86 :param flag: name of the query or edit flag 87 :param isEdit: If not False, the method returned will be an edit function 88 :param methodName: the name of the method returned, defaults to inCmd name """ 89 90 func = None 91 if isEdit: 92 def editFunc(self, val, **kwargs): 93 kwargs[ 'edit' ] = True 94 kwargs[ flag ] = val 95 return inCmd( self, **kwargs )
96 97 func = editFunc 98 # END if edit 99 else: 100 def queryFunc(self, **kwargs): 101 kwargs[ 'query' ] = True 102 kwargs[ flag ] = True 103 return inCmd( self, **kwargs ) 104 105 func = queryFunc 106 # END if query 107 108 if not methodName: 109 methodName = flag 110 func.__name__ = methodName 111 112 return func 113
114 -def queryMethod( inCmd, flag, methodName = None ):
115 """ Shorthand query version of makeEditOrQueryMethod """ 116 return makeEditOrQueryMethod( inCmd, flag, isEdit=False, methodName=methodName )
117
118 -def editMethod( inCmd, flag, methodName = None ):
119 """ Shorthand edit version of makeEditOrQueryMethod """ 120 return makeEditOrQueryMethod( inCmd, flag, isEdit=True, methodName=methodName )
121
122 -def propertyQE( inCmd, flag, methodName = None ):
123 """ Shorthand for simple query and edit properties """ 124 editFunc = editMethod( inCmd, flag, methodName = methodName ) 125 queryFunc = queryMethod( inCmd, flag, methodName = methodName ) 126 return property( queryFunc, editFunc )
127
128 #} END mel function wrappers 129 130 131 #{ Utitliy Classes 132 133 -class Mel(util.Singleton):
134 """This class is a necessity for calling mel scripts from python. It allows scripts to be called 135 in a cleaner fashion, by automatically formatting python arguments into a string 136 which is executed via maya.mel.eval(). 137 138 :note: originated from pymel, added customizations """ 139
140 - def __getattr__(self, command):
141 """Only for instances of this class - call methods directly as if they where 142 attributes """ 143 if command.startswith('__') and command.endswith('__'): 144 return self.__dict__[command] 145 def _call(*args): 146 147 strArgs = map( pythonToMel, args) 148 149 cmd = '%s(%s)' % ( command, ','.join( strArgs ) ) 150 try: 151 return mm.eval(cmd) 152 except RuntimeError, msg: 153 info = self.whatIs( command ) 154 if info.startswith( 'Presumed Mel procedure'): 155 raise NameError, 'Unknown Mel procedure' 156 raise RuntimeError, msg
157 158 return _call
159 160 @staticmethod
161 - def call( command, *args ):
162 """ Call a mel script , very simpilar to Mel.myscript( args ) 163 164 :todo: more docs """ 165 strArgs = map( pythonToMel, args) 166 167 cmd = '%s(%s)' % ( command, ','.join( strArgs ) ) 168 169 try: 170 return mm.eval(cmd) 171 except RuntimeError, msg: 172 info = Mel.call( "whatIs", command ) 173 if info.startswith( 'Presumed Mel procedure'): 174 raise NameError, ( 'Unknown Mel procedure: ' + cmd ) 175 raise RuntimeError, msg
176 177 @staticmethod
178 - def mprint(*args):
179 """mel print command in case the python print command doesn't cut it. i have noticed that python print does not appear 180 in certain output, such as the rush render-queue manager.""" 181 mm.eval( r"""print (%s);""" % pythonToMel( ' '.join( map( str, args))) + '\n' )
182 183 @staticmethod
184 - def eval( command ):
185 """ same as maya.mel eval """ 186 return mm.eval( command )
187 188 @staticmethod
189 - def _melprint( cmd, msg ):
190 mm.eval( """%s %s""" % ( cmd, pythonToMel( msg ) ) )
191 192 error = staticmethod( lambda *args: Mel._melprint( "error", *args ) ) 193 trace = staticmethod( lambda *args: Mel._melprint( "trace", *args ) ) 194 info = staticmethod( lambda *args: Mel._melprint( "print", *args ) ) 195
196 197 -class OptionVarDict( util.Singleton ):
198 """ A singleton dictionary-like class for accessing and modifying optionVars. 199 200 :note: Idea and base Implementation from PyMel, modified to adapt to mrv """
201 - class OptionVarList(tuple):
202 - def __new__( cls, key, val ):
203 """modify constructor to work with tuple""" 204 newinstpreinit = tuple.__new__( cls, val ) 205 newinstpreinit.key = key 206 return newinstpreinit
207
208 - def appendVar( self, val ):
209 """ values appended to the OptionVarList with this method will be 210 added to the Maya optionVar at the key denoted by self.key. """ 211 if isinstance( val, basestring): 212 return cmds.optionVar( stringValueAppend=[self.key,unicode(val)] ) 213 if isinstance( val, (bool,int) ): 214 return cmds.optionVar( intValueAppend=[self.key,int(val)] ) 215 if isinstance( val, float): 216 return cmds.optionVar( floatValueAppend=[self.key,val] )
217 218
219 - def __contains__(self, key):
220 return cmds.optionVar( exists=key )
221
222 - def __getitem__(self, key):
223 if key not in self: 224 raise KeyError("OptionVar named %s did not exist" % key) 225 # END raise if k not in d 226 227 val = cmds.optionVar( q=key ) 228 if isinstance(val, list): 229 val = self.OptionVarList( key, val ) 230 return val
231
232 - def __setitem__(self,key,val):
233 if isinstance( val, basestring): 234 return cmds.optionVar( stringValue=[key,unicode(val)] ) 235 if isinstance( val, (int,bool)): 236 return cmds.optionVar( intValue=[key,int(val)] ) 237 if isinstance( val, float): 238 return cmds.optionVar( floatValue=[key,val] ) 239 240 if isinstance( val, (list,tuple) ): 241 if len(val) == 0: 242 return cmds.optionVar( clearArray=key ) 243 244 if isinstance( val[0], basestring): 245 cmds.optionVar( stringValue=[key,unicode(val[0])] ) # force to this datatype 246 for elem in val[1:]: 247 cmds.optionVar( stringValueAppend=[key,unicode(elem)] ) 248 return 249 250 if isinstance( val[0], (int,bool)): 251 cmds.optionVar( intValue=[key,int(val[0])] ) # force to this datatype 252 for elem in val[1:]: 253 cmds.optionVar( intValueAppend=[key,int(elem)] ) 254 return 255 256 if isinstance( val[0], float): 257 cmds.optionVar( floatValue=[key,val[0]] ) # force to this datatype 258 for elem in val[1:]: 259 cmds.optionVar( floatValueAppend=[key,float(elem)] ) 260 return 261 262 raise TypeError, 'unsupported datatype: strings, ints, float, lists, and their subclasses are supported'
263
264 - def __delitem__( self, key ):
265 """Delete the optionvar identified by key""" 266 cmds.optionVar( rm = str( key ) )
267
268 - def keys(self):
269 return cmds.optionVar( list=True )
270
271 - def iterkeys( self ):
272 """:return: iterator to option var names""" 273 return iter( self.keys() )
274
275 - def itervalues( self ):
276 """:return: iterator to optionvar values""" 277 for key in self.iterkeys(): 278 yield self[ key ]
279
280 - def iteritems( self ):
281 """:return: iterators to tuple of key,value pairs""" 282 for key in self.iterkeys(): 283 yield ( key, self[ key ] )
284
285 - def get(self, key, default=None):
286 if self.has_key(key): 287 return self[key] 288 else: 289 return default
290
291 - def has_key(self, key):
292 return cmds.optionVar( exists=key )
293
294 - def pop(self, key):
295 val = self[ key ] 296 del( self[ key ] ) 297 return val
298 299 300 # use it as singleton 301 optionvars = OptionVarDict()
302 303 #} END utility classes 304 305 306 #{ API Utilities Classes 307 308 -class StandinClass( object ):
309 """ Simple Function Object allowing to embed the name of the type as well as 310 the metaclass object supposed to create the actual class. It mus be able to completely 311 create the given class. 312 313 :note: Use it at placeholder for classes that are to be created on first call, without 314 vasting large amounts of memory if one wants to precreate them.""" 315 __slots__ = ( "clsname", "classcreator", "_createdClass" ) 316
317 - def __init__( self, classname, classcreator=type ):
318 self.clsname = classname 319 self.classcreator = classcreator 320 self._createdClass = None
321
322 - def createCls( self ):
323 """ Create the class of type self.clsname using our classcreator - can only be called once ! 324 325 :return : the newly created class""" 326 if self._createdClass is None: 327 self._createdClass = self.classcreator( self.clsname, tuple(), {} ) 328 329 return self._createdClass
330
331 - def __call__( self, *args, **kwargs ):
332 newcls = self.createCls( ) 333 return newcls( *args, **kwargs )
334
335 336 -class MetaClassCreator( type ):
337 """ Builds the base hierarchy for the given classname based on our 338 typetree """ 339
340 - def __new__( dagtree, module, metacls, name, bases, clsdict, 341 nameToTreeFunc=uncapitalize, treeToNameFunc=capitalize ):
342 """Create a new class from hierarchy information found in dagtree and 343 put it into the module if it not yet exists 344 345 :param dagtree: `mrv.util.DAGTree` instance with hierarchy information 346 :param module: the module instance to which to add the new classes to 347 :param nameToTreeFunc: convert the class name to a name suitable for dagTree look-up 348 :param treeToNameFunc: convert a value from the dag tree into a valid class name ( used for parent lookup )""" 349 # recreate the hierarchy of classes leading to the current type 350 nameForTree = nameToTreeFunc( name ) 351 parentname = None 352 try: 353 parentname = dagtree.parent( nameForTree ) 354 except networkxexc.NetworkXError: 355 # we can handle and thus ignore key errors, mostly created by subclass 356 # of our wrapped classes 357 pass 358 # END parent name handling 359 360 parentcls = None 361 362 # ASSURE PARENTS 363 ##################### 364 # Parent classes must be available in advance 365 if parentname is not None: 366 parentclsname = treeToNameFunc( parentname ) 367 parentcls = module.__dict__[ parentclsname ] 368 if isinstance( parentcls, StandinClass ): 369 parentcls = parentcls.createCls( ) 370 # END if parent cls name defined 371 372 # could be a user-defined class coming with some parents already - thus assure 373 # that the auto-parent is not already in there 374 # NOTE: bases is sometimes filled with types, sometimes with classes 375 if parentcls and not ( parentcls in bases or isinstance( parentcls, bases ) ): 376 bases += ( parentcls, ) 377 378 # create the class 379 newcls = super( MetaClassCreator, metacls ).__new__( metacls, str( name ), bases, clsdict ) 380 381 # change the module - otherwise it will get our module 382 newcls.__module__ = module.__name__ 383 384 # replace the dummy class in the module 385 module.__dict__[ name ] = newcls 386 387 388 389 return newcls
390
391 -class CallbackEventBase( util.Event ):
392 """Allows the mapping of MMessage callbacks to mrv's event sender system. 393 This event will register a new message once the first event receiver registers 394 itself. Once the last event receiver deregisters, the message will be deregistered in 395 maya as well. 396 397 Derived types have to implement the `_getRegisterFunction` 398 399 :note: Its important that you care about deregistering your event to make sure the maya event can 400 be deregistered. Its worth knowing that the eventSender in question is strongly 401 bound to his callback event, so it cannot be deleted while the event is active.""" 402 403 #{ Utility Classes
404 - class CBStorageFunction(object):
405 __slots__ = '_callbackID'
406 - def __init__(self, callbackID=None):
408
409 - def setCallbackID(self, callbackID):
410 self._callbackID = callbackID
411
412 - def callbackID(self):
413 return self._callbackID
414
415 - def removeCallback(self):
416 if self._callbackID: 417 api.MMessage.removeCallback(self._callbackID) 418 # prevent memory leak message if possible 419 if hasattr(self._callbackID, 'disown'): 420 self._callbackID.disown() 421 self._callbackID = None
422 # END 423
424 - def __call__(self, *args, **kwargs):
425 return self._callbackID
426 #} END utility classes 427
428 - def __init__( self, eventId, **kwargs ):
429 """Initialize our instance with the callbackID we are to represent.""" 430 super( CallbackEventBase, self ).__init__( **kwargs ) 431 self._eventID = eventId
432 433 #{ Subclass Implementation Needed
434 - def _getRegisterFunction(self, eventID):
435 """ 436 :return: MMessage::register* compatible callback function which can be 437 used to register the given eventID""" 438 raise NotImplementedError("To be implemented in subclass")
439 440 #} END subclass implementation needed 441 442 #{ CallbackID handling
443 - def _storeCallbackID(self, inst, callbackID):
444 """Store the given callbackID in the event sender instance. 445 We do that by registering it as function for the given instance which 446 447 :return: the callback ID on call""" 448 storage = self._getCallbackIDStorage(inst, create=True) 449 storage.setCallbackID(callbackID)
450
451 - def _getCallbackIDStorage(self, inst, create=False):
452 """ 453 :return: Callback storage function if it exists or None 454 :param create: if True, the storage will be created if needed, hence 455 you will always receive a valid storage""" 456 functions = self._getFunctionSet(inst) 457 storage_functions = [ cb for cb in functions if isinstance(cb, self.CBStorageFunction) ] 458 if not storage_functions: 459 if not create: 460 return None 461 462 sf = self.CBStorageFunction() 463 functions.add(sf) 464 return sf 465 # END handle storage does not exists 466 467 assert len(storage_functions) == 1, "Expecting only one storage function, found %i" % len(storage_functions) 468 return storage_functions[0]
469
470 - def _getCallbackID(self, inst):
471 """:return: stored callback ID or None""" 472 storage = self._getCallbackIDStorage(inst) 473 if storage is None: 474 return None 475 return storage.callbackID()
476 477 #} END handle callback ID 478 479
480 - def send( self, inst, *args, **kwargs ):
481 """Sets our instance prior to calling the super class 482 483 :note: must not be called manually""" 484 # fake the instance 485 self._last_inst_ref = weakref.ref(inst) 486 super(CallbackEventBase, self).send(*args, **kwargs)
487
488 - def __set__( self, inst, eventfunc ):
489 eventset = self._getFunctionSet( inst ) 490 491 # REGISTER MCALLBACK IF THIS IS THE FIRST EVENTRECEIVER 492 if not eventset: 493 reg_method = self._getRegisterFunction(self._eventID) 494 495 dyncall = lambda *args, **kwargs: self.send(inst, *args, **kwargs) 496 callbackID = reg_method(self._eventID, dyncall) 497 self._storeCallbackID(inst, callbackID) 498 # END create event 499 500 super( CallbackEventBase, self ).__set__( inst, eventfunc )
501
502 - def remove(self, eventfunc):
503 """Also removes our callback if the last receiver is gone""" 504 super(CallbackEventBase, self).remove(eventfunc) 505 inst = self._get_last_instance() 506 functions = self._getFunctionSet( inst ) 507 508 # if there is only one item left, this should be our storage function 509 if len(functions) == 1: 510 cbstorage = iter(functions).next() 511 assert isinstance(cbstorage, self.CBStorageFunction) 512 513 cbstorage.removeCallback() 514 functions.remove(cbstorage)
515 # END dergister event 585 586 #} END api utility classes 587