2
"""All kinds of utility methods and classes that are used in more than one modules """
3
__docformat__ = "restructuredtext"
6
import maya.cmds as cmds
7
import maya.OpenMaya as api
9
from mrv.util import capitalize,uncapitalize
10
import networkx.exception as networkxexc
16
__all__ = ("noneToList", "isIterable", "pythonToMel", "makeEditOrQueryMethod",
17
"queryMethod", "editMethod", "propertyQE", "Mel", "OptionVarDict",
18
"optionvars", "StandinClass", "MetaClassCreator", "CallbackEventBase",
19
"MEnumeration", "notifyException")
23
""":return: list instead of None"""
29
return hasattr(obj,'__iter__') and not isinstance(obj,basestring)
32
if isinstance(arg,basestring):
33
return u'"%s"' % cmds.encodeString(arg)
35
return u'{%s}' % ','.join( map( pythonToMel, arg) )
37
#} END utility functions
42
def _logException(func, reraise):
43
def wrapper(*args, **kwargs):
45
return func(*args, **kwargs)
47
# print a full stack trace - for now not using the log
51
if api.MGlobal.mayaState() == api.MGlobal.kInteractive:
52
import mrv.maya.ui as ui
55
msg += "\n\nSee the script editor for details"
56
ui.ChoiceDialog( t=str(type(e)),
58
c=['Confirm'] ).choice()
63
wrapper.__name__ = func.__name__
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)
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)
79
#{ MEL Function Wrappers
81
def makeEditOrQueryMethod( inCmd, flag, isEdit=False, methodName=None ):
82
"""Create a function calling inFunc with an edit or query flag set.
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 """
92
def editFunc(self, val, **kwargs):
93
kwargs[ 'edit' ] = True
95
return inCmd( self, **kwargs )
100
def queryFunc(self, **kwargs):
101
kwargs[ 'query' ] = True
102
kwargs[ flag ] = True
103
return inCmd( self, **kwargs )
110
func.__name__ = methodName
114
def queryMethod( inCmd, flag, methodName = None ):
115
""" Shorthand query version of makeEditOrQueryMethod """
116
return makeEditOrQueryMethod( inCmd, flag, isEdit=False, methodName=methodName )
118
def editMethod( inCmd, flag, methodName = None ):
119
""" Shorthand edit version of makeEditOrQueryMethod """
120
return makeEditOrQueryMethod( inCmd, flag, isEdit=True, methodName=methodName )
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 )
128
#} END mel function wrappers
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().
138
:note: originated from pymel, added customizations """
140
def __getattr__(self, command):
141
"""Only for instances of this class - call methods directly as if they where
143
if command.startswith('__') and command.endswith('__'):
144
return self.__dict__[command]
147
strArgs = map( pythonToMel, args)
149
cmd = '%s(%s)' % ( command, ','.join( strArgs ) )
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
161
def call( command, *args ):
162
""" Call a mel script , very simpilar to Mel.myscript( args )
165
strArgs = map( pythonToMel, args)
167
cmd = '%s(%s)' % ( command, ','.join( strArgs ) )
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
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' )
185
""" same as maya.mel eval """
186
return mm.eval( command )
189
def _melprint( cmd, msg ):
190
mm.eval( """%s %s""" % ( cmd, pythonToMel( msg ) ) )
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 ) )
197
class OptionVarDict( util.Singleton ):
198
""" A singleton dictionary-like class for accessing and modifying optionVars.
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
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] )
219
def __contains__(self, key):
220
return cmds.optionVar( exists=key )
222
def __getitem__(self, key):
224
raise KeyError("OptionVar named %s did not exist" % key)
225
# END raise if k not in d
227
val = cmds.optionVar( q=key )
228
if isinstance(val, list):
229
val = self.OptionVarList( key, val )
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] )
240
if isinstance( val, (list,tuple) ):
242
return cmds.optionVar( clearArray=key )
244
if isinstance( val[0], basestring):
245
cmds.optionVar( stringValue=[key,unicode(val[0])] ) # force to this datatype
247
cmds.optionVar( stringValueAppend=[key,unicode(elem)] )
250
if isinstance( val[0], (int,bool)):
251
cmds.optionVar( intValue=[key,int(val[0])] ) # force to this datatype
253
cmds.optionVar( intValueAppend=[key,int(elem)] )
256
if isinstance( val[0], float):
257
cmds.optionVar( floatValue=[key,val[0]] ) # force to this datatype
259
cmds.optionVar( floatValueAppend=[key,float(elem)] )
262
raise TypeError, 'unsupported datatype: strings, ints, float, lists, and their subclasses are supported'
264
def __delitem__( self, key ):
265
"""Delete the optionvar identified by key"""
266
cmds.optionVar( rm = str( key ) )
269
return cmds.optionVar( list=True )
271
def iterkeys( self ):
272
""":return: iterator to option var names"""
273
return iter( self.keys() )
275
def itervalues( self ):
276
""":return: iterator to optionvar values"""
277
for key in self.iterkeys():
280
def iteritems( self ):
281
""":return: iterators to tuple of key,value pairs"""
282
for key in self.iterkeys():
283
yield ( key, self[ key ] )
285
def get(self, key, default=None):
286
if self.has_key(key):
291
def has_key(self, key):
292
return cmds.optionVar( exists=key )
301
optionvars = OptionVarDict()
303
#} END utility classes
306
#{ API Utilities Classes
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.
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" )
317
def __init__( self, classname, classcreator=type ):
318
self.clsname = classname
319
self.classcreator = classcreator
320
self._createdClass = None
322
def createCls( self ):
323
""" Create the class of type self.clsname using our classcreator - can only be called once !
325
:return : the newly created class"""
326
if self._createdClass is None:
327
self._createdClass = self.classcreator( self.clsname, tuple(), {} )
329
return self._createdClass
331
def __call__( self, *args, **kwargs ):
332
newcls = self.createCls( )
333
return newcls( *args, **kwargs )
336
class MetaClassCreator( type ):
337
""" Builds the base hierarchy for the given classname based on our
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
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 )
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
358
# END parent name handling
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
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, )
379
newcls = super( MetaClassCreator, metacls ).__new__( metacls, str( name ), bases, clsdict )
381
# change the module - otherwise it will get our module
382
newcls.__module__ = module.__name__
384
# replace the dummy class in the module
385
module.__dict__[ name ] = newcls
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
397
Derived types have to implement the `_getRegisterFunction`
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."""
404
class CBStorageFunction(object):
405
__slots__ = '_callbackID'
406
def __init__(self, callbackID=None):
407
self.setCallbackID(callbackID)
409
def setCallbackID(self, callbackID):
410
self._callbackID = callbackID
412
def callbackID(self):
413
return self._callbackID
415
def removeCallback(self):
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
424
def __call__(self, *args, **kwargs):
425
return self._callbackID
426
#} END utility classes
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
433
#{ Subclass Implementation Needed
434
def _getRegisterFunction(self, eventID):
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")
440
#} END subclass implementation needed
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
447
:return: the callback ID on call"""
448
storage = self._getCallbackIDStorage(inst, create=True)
449
storage.setCallbackID(callbackID)
451
def _getCallbackIDStorage(self, inst, create=False):
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:
462
sf = self.CBStorageFunction()
465
# END handle storage does not exists
467
assert len(storage_functions) == 1, "Expecting only one storage function, found %i" % len(storage_functions)
468
return storage_functions[0]
470
def _getCallbackID(self, inst):
471
""":return: stored callback ID or None"""
472
storage = self._getCallbackIDStorage(inst)
475
return storage.callbackID()
477
#} END handle callback ID
480
def send( self, inst, *args, **kwargs ):
481
"""Sets our instance prior to calling the super class
483
:note: must not be called manually"""
485
self._last_inst_ref = weakref.ref(inst)
486
super(CallbackEventBase, self).send(*args, **kwargs)
488
def __set__( self, inst, eventfunc ):
489
eventset = self._getFunctionSet( inst )
491
# REGISTER MCALLBACK IF THIS IS THE FIRST EVENTRECEIVER
493
reg_method = self._getRegisterFunction(self._eventID)
495
dyncall = lambda *args, **kwargs: self.send(inst, *args, **kwargs)
496
callbackID = reg_method(self._eventID, dyncall)
497
self._storeCallbackID(inst, callbackID)
500
super( CallbackEventBase, self ).__set__( inst, eventfunc )
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 )
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)
513
cbstorage.removeCallback()
514
functions.remove(cbstorage)
515
# END dergister event
518
class MEnumeration(tuple):
519
"""Simple enumeration class which allows access to its enumeration using
521
As it is a tuple as well, one can access the enumeration values in the right sequencial
523
def __new__(cls, sequence, name=''):
524
inst = super(MEnumeration, cls).__new__(cls, sequence)
532
return "MEnumeration(%s)" % self.name
536
def nameByValue(self, value):
537
""":return: name string with the given integer value
538
:param value: integer value of this enumeration
539
:raise ValueError: if value is not in the enumeration"""
540
for n,v in self.__dict__.items():
541
if not n.startswith('k') or not isinstance(v, int):
546
# END if value matches
547
# END for each item in our dict
548
raise ValueError("Value %i not in enumeration" % value)
553
def create( cls, ed, mfncls ):
555
:return: new instance of this type as initialized from the EnumDescriptor ed and
558
emembers = list() # temporary
560
# get the values in the right sequence
563
ev = getattr(mfncls, em)
565
# END for each enumeration member
566
except AttributeError:
567
# happens in 2008+ as they have ifdeffed items that we pick up,
568
# but which are somewhat inoffical
570
# END exception handling
572
enum = cls(emembers, name=ed.name)
574
# assign each member by name
577
ev = getattr(mfncls, em)
578
setattr(enum, em, ev)
579
# END for each enumeration member
580
except AttributeError:
582
# END exception handlign
586
#} END api utility classes