mrv.maya.util
Covered: 326 lines
Missed: 71 lines
Skipped 190 lines
Percent: 82 %
  2
"""All kinds of utility methods and classes that are used in more than one modules """
  3
__docformat__ = "restructuredtext"
  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
 12
import traceback
 14
import weakref
 16
__all__ = ("noneToList", "isIterable", "pythonToMel", "makeEditOrQueryMethod", 
 17
           "queryMethod", "editMethod", "propertyQE", "Mel", "OptionVarDict", 
 18
           "optionvars", "StandinClass", "MetaClassCreator", "CallbackEventBase", 
 19
           "MEnumeration", "notifyException")
 22
def noneToList( res ):
 23
	""":return: list instead of None"""
 24
	if res is None:
 25
		return list()
 26
	return res
 28
def isIterable( obj ):
 29
	return hasattr(obj,'__iter__') and not isinstance(obj,basestring)
 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)
 42
def _logException(func, reraise):
 43
	def wrapper(*args, **kwargs):
 44
		try:
 45
			return func(*args, **kwargs)
 46
		except Exception, e:
 48
			if reraise:
 49
				traceback.print_exc()
 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()
 60
			if reraise:
 61
				raise
 63
	wrapper.__name__ = func.__name__
 64
	return wrapper
 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)
 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  """
 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 )
 97
		func = editFunc
 99
	else:
100
		def queryFunc(self, **kwargs):
101
			kwargs[ 'query' ] = True
102
			kwargs[ flag ] = True
103
			return inCmd( self, **kwargs )
105
		func = queryFunc
108
	if not methodName:
109
		methodName = flag
110
	func.__name__ = methodName
112
	return func
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 )
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
142
		attributes """
143
		if command.startswith('__') and command.endswith('__'):
144
			return self.__dict__[command]
145
		def _call(*args):
147
			strArgs = map( pythonToMel, args)
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
158
		return _call
160
	@staticmethod
161
	def call( command, *args ):
162
		""" Call a mel script , very simpilar to Mel.myscript( args )
164
		:todo: more docs """
165
		strArgs = map( pythonToMel, args)
167
		cmd = '%s(%s)' % ( command, ','.join( strArgs ) )
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
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' )
183
	@staticmethod
184
	def eval( command ):
185
		""" same as maya.mel eval """
186
		return mm.eval( command )
188
	@staticmethod
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):
223
		if key not in self:
224
			raise KeyError("OptionVar named %s did not exist" % key)
227
		val = cmds.optionVar( q=key )
228
		if isinstance(val, list):
229
			val = self.OptionVarList( key, val )
230
		return 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) ):
241
			if len(val) == 0:
242
				return cmds.optionVar( clearArray=key )
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
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
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
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 ) )
268
	def keys(self):
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():
278
			yield self[ key ]
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):
287
			return self[key]
288
		else:
289
			return default
291
	def has_key(self, key):
292
		return cmds.optionVar( exists=key )
294
	def pop(self, key):
295
		val = self[ key ]
296
		del( self[ key ] )
297
		return val
301
optionvars = OptionVarDict()
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
338
	typetree """
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 )"""
350
		nameForTree = nameToTreeFunc( name )
351
		parentname = None
352
		try:
353
			parentname = dagtree.parent( nameForTree )
354
		except networkxexc.NetworkXError:
357
			pass
360
		parentcls = None
365
		if parentname is not None:
366
			parentclsname = treeToNameFunc( parentname )
367
			parentcls = module.__dict__[ parentclsname ]
368
			if isinstance( parentcls, StandinClass ):
369
				parentcls = parentcls.createCls( )
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 )
382
		newcls.__module__ = module.__name__
385
		module.__dict__[ name ] = newcls
389
		return 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 
395
	maya as well.
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):
416
			if self._callbackID:
417
				api.MMessage.removeCallback(self._callbackID)
419
				if hasattr(self._callbackID, 'disown'):
420
					self._callbackID.disown()
421
				self._callbackID = None
424
		def __call__(self, *args, **kwargs):
425
			return self._callbackID
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
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")
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):
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
462
			sf = self.CBStorageFunction()
463
			functions.add(sf)
464
			return sf
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)
473
		if storage is None:
474
			return None
475
		return storage.callbackID()
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 )
492
		if not eventset:
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 )
509
		if len(functions) == 1:
510
			cbstorage = iter(functions).next()
511
			assert isinstance(cbstorage, self.CBStorageFunction)
513
			cbstorage.removeCallback()
514
			functions.remove(cbstorage)
518
class MEnumeration(tuple):
519
	"""Simple enumeration class which allows access to its enumeration using 
520
	getattr access. 
521
	As it is a tuple as well, one can access the enumeration values in the right sequencial
522
	order"""
523
	def __new__(cls, sequence, name=''):
524
		inst = super(MEnumeration, cls).__new__(cls, sequence)
525
		inst.name = name
526
		return inst
528
	def __str__(self):
529
		return self.name
531
	def __repr__(self):
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):
542
				continue
544
			if v == value:
545
				return n
548
		raise ValueError("Value %i not in enumeration" % value)
552
	@classmethod
553
	def create( cls, ed, mfncls ):
554
		"""
555
		:return: new instance of this type as initialized from the EnumDescriptor ed and 
556
			the mfncls
557
		"""
558
		emembers = list()		# temporary
561
		try:
562
			for em in ed:
563
				ev = getattr(mfncls, em)
564
				emembers.append(ev)
566
		except AttributeError:
569
			pass
572
		enum = cls(emembers, name=ed.name)
575
		try:
576
			for em in ed:
577
				ev = getattr(mfncls, em)
578
				setattr(enum, em, ev)
580
		except AttributeError:
581
			pass
584
		return enum