mrv.maya.nt.apipatch
Covered: 794 lines
Missed: 25 lines
Skipped 358 lines
Percent: 96 %
   2
"""
   3
Contains patch classes that are altering their respective api classes
   5
The classes here are rather verbose and used as patch-template which can be
   6
handled correctly by epydoc, and whose method will be used to patch the respective
   7
api classes.
   9
As they are usually derived from the class they patch , they could also be used directly
  11
:note: **never import classes directly in here**, import the module instead, thus
  12
	**not**: thisImportedClass **but**: module.thisImportedClass !
  13
"""
  14
__docformat__ = "restructuredtext"
  16
import base
  17
import mrv.maya.undo as undo
  18
import mrv.util as util
  19
from mrv.interface import iDagItem
  21
import maya.OpenMaya as api
  22
import maya.cmds as cmds
  24
import inspect
  25
import itertools
  26
import it
  27
import os
  33
def init_applyPatches( ):
  34
	"""Called by package __init__ method to finally apply the patch according to
  35
	the template classes
  36
	Template classes must derive from the to-be-patched api class first, and can derive
  37
	from helper classes providing basic patch methods.
  38
	Helper classes must derive from Abstract to indicate their purpose
  40
	If a class has an _applyPatch method, it will be called and not additional. If
  41
	it returns True, the class members will be applied as usual, if False the method will stop
  43
	:note: overwritten api methods will be renamed to _api_methodname
  44
	:note: currently this method works not recursively"""
  45
	module = __import__( "mrv.maya.nt.apipatch", globals(), locals(), ['apipatch'] )
  46
	classes = [ v for v in globals().values() if inspect.isclass(v) ]
  47
	forbiddenMembers = [ '__module__','_applyPatch','__dict__','__weakref__','__doc__' ]
  48
	apply_globally = int(os.environ.get('MRV_APIPATCH_APPLY_GLOBALLY', 0))
  50
	ns = None
  51
	if apply_globally:
  52
		ns = 'm'
  56
	for cls in classes:
  59
		templateclasses = [ cls ]
  60
		templateclasses.extend( cls.__bases__[ 1: ] )
  64
		templateclasses.reverse()
  67
		if cls is Abstract or cls.__bases__[0] is Abstract:
  68
			continue
  70
		apicls = cls.__bases__[0]
  74
		if hasattr( cls, "_applyPatch" ):
  75
			if not cls._applyPatch(  ):
  76
				continue
  78
		for tplcls in templateclasses:
  79
			util.copyClsMembers( tplcls, apicls, overwritePrefix="_api_",
  80
										forbiddenMembers = forbiddenMembers, 
  81
										copyNamespaceGlobally=ns)
  84
	pass
  87
class Abstract:
  88
	"""Class flagging that subclasses should be abstract and are only to be used
  89
	as superclass """
  90
	pass
  94
class TimeDistanceAngleBase( Abstract ):
  95
	"""Base patch class for all indicated classes
  97
	:note: idea for patches from pymel"""
  98
	def __str__( self ): return str(float(self))
  99
	def __int__( self ): return int(float(self))
 103
	if hasattr(api.MTime, 'asUnits'):
 104
		def __float__( self ): return self.asUnits(self.uiUnit())
 105
	else:
 106
		def __float__( self ): return getattr(self, 'as')(self.uiUnit())
 108
	def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, float(self) )
 111
class MTime( api.MTime, TimeDistanceAngleBase ) :
 112
	pass
 114
class MDistance( api.MDistance, TimeDistanceAngleBase ) :
 115
	pass
 117
class MAngle( api.MAngle, TimeDistanceAngleBase ) :
 118
	pass
 122
class PatchIterablePrimitives( Abstract ):
 123
	""":note: Classes derived from this base should not be used directly"""
 124
	@classmethod
 125
	def _applyPatch( cls ):
 126
		"""Read per-class values from self and create appropriate methods and
 127
		set them as well
 129
		:note: idea from pymel"""
 130
		def __len__(self):
 131
			""" Number of components in Maya api iterable """
 132
			return self._length
 134
		type.__setattr__( cls.__bases__[0], '__len__', __len__ )
 136
		def __iter__(self):
 137
			""" Iterates on all components of a Maya base iterable """
 138
			for i in range( self._length ) :
 139
				yield self.__getitem__( i )
 141
		type.__setattr__( cls.__bases__[0], '__iter__', __iter__)
 143
		def __str__( self ):
 144
			return "[ %s ]" % " ".join( str( f ) for f in self )
 147
		type.__setattr__( cls.__bases__[0], '__str__', __str__)
 149
		def __repr__( self ):
 150
			return "%s([ %s ])" % (type(self).__name__, " ".join( str( f ) for f in self ))
 153
		type.__setattr__( cls.__bases__[0], '__repr__', __repr__)
 156
		return True
 158
class PatchMatrix( Abstract, PatchIterablePrimitives ):
 159
	"""Only for matrices"""
 160
	@classmethod
 161
	def _applyPatch( cls ):
 162
		"""Special version for matrices"""
 163
		PatchIterablePrimitives._applyPatch.im_func( cls )
 164
		def __iter__(self):
 165
			""" Iterates on all 4 rows of a Maya api MMatrix """
 166
			for r in range( self._length ) :
 167
				row = self.__getitem__( r )
 168
				yield [ self.scriptutil( row, c ) for c in range( self._length ) ]
 170
		type.__setattr__( cls.__bases__[0], '__iter__', __iter__ )
 173
		def __str__( self ):
 174
			return "\n".join( str( v ) for v in self )
 177
		type.__setattr__( cls.__bases__[0], '__str__', __str__)
 179
		return True
 183
class MVector( api.MVector, PatchIterablePrimitives ):
 184
	_length =3
 186
class MFloatVector( api.MFloatVector, PatchIterablePrimitives ):
 187
	_length =3
 189
class MPoint( api.MPoint, PatchIterablePrimitives ):
 190
	_length =4
 192
class MFloatPoint( api.MFloatPoint, PatchIterablePrimitives ):
 193
	_length =4
 195
class MColor( api.MColor, PatchIterablePrimitives ):
 196
	_length =4
 198
class MQuaternion( api.MQuaternion, PatchIterablePrimitives ):
 199
	_length =4
 201
class MEulerRotation( api.MEulerRotation, PatchIterablePrimitives ):
 202
	_length =4
 204
class MMatrix( api.MMatrix, PatchMatrix ):
 205
	_length =4
 206
	scriptutil = api.MScriptUtil.getDoubleArrayItem
 208
class MFloatMatrix( api.MFloatMatrix, PatchMatrix ):
 209
	_length =4
 210
	scriptutil = api.MScriptUtil.getFloatArrayItem
 212
class MTransformationMatrix( api.MTransformationMatrix, PatchMatrix ):
 213
	_length =4
 215
	@classmethod
 216
	def _applyPatch( cls ):
 217
		"""Special version for matrices"""
 218
		PatchMatrix._applyPatch.im_func( cls )
 219
		def __iter__(self):
 220
			""" Iterates on all 4 rows of a Maya api MMatrix """
 221
			return self.asMatrix().__iter__()
 223
		type.__setattr__( cls.__bases__[0], '__iter__', __iter__ )
 224
		return True
 226
	def mgetScale( self , space = api.MSpace.kTransform ):
 227
		ms = api.MScriptUtil()
 228
		ms.createFromDouble( 1.0, 1.0, 1.0 )
 229
		p = ms.asDoublePtr()
 230
		self.getScale( p, space );
 231
		return MVector( *( ms.getDoubleArrayItem (p, i) for i in range(3) ) )
 233
	def msetScale( self, value, space = api.MSpace.kTransform ):
 234
		ms = api.MScriptUtil()
 235
		ms.createFromDouble( *value )
 236
		p = ms.asDoublePtr()
 237
		self.setScale ( p, space )
 239
	def getTranslation( self, space = api.MSpace.kTransform ):
 240
		"""This patch is fully compatible to the default method"""
 241
		return self._api_getTranslation( space )
 243
	def setTranslation( self, vector, space = api.MSpace.kTransform ):
 244
		"""This patch is fully compatible to the default method"""
 245
		return self._api_setTranslation( vector, space )
 251
def _mplug_createUndoSetFunc( dataTypeId, getattroverride = None ):
 252
	"""Create a function setting a value with undo support
 254
	:param dataTypeId: string naming the datatype, like "Bool" - capitalization is
 255
		important
 256
	:note: if undo is globally disabled, we will resolve to implementing a faster
 257
		function instead as we do not store the previous value.
 258
	:note: to use the orinal method without undo, use api.MPlug.setX(your_plug, value)"""
 260
	getattrfunc = getattroverride
 261
	if not getattrfunc:
 262
		getattrfunc = getattr( api.MPlug, "as"+dataTypeId )
 263
	setattrfunc = getattr( api.MPlug, "set"+dataTypeId )
 268
	finalWrappedSetAttr = None
 269
	if dataTypeId == "MObject":
 270
		def wrappedSetAttr( self, data ):
 272
			try:
 273
				curdata = getattrfunc( self )
 274
			except RuntimeError:
 275
				curdata = api.MObject()
 276
			op = undo.GenericOperation( )
 278
			op.setDoitCmd( setattrfunc, self, data )
 279
			op.setUndoitCmd( setattrfunc, self, curdata )
 281
			op.doIt()
 283
		finalWrappedSetAttr = wrappedSetAttr
 284
	else:
 285
		def wrappedSetAttr( self, data ):
 287
			curdata = getattrfunc( self )
 288
			op = undo.GenericOperation( )
 290
			op.setDoitCmd( setattrfunc, self, data )
 291
			op.setUndoitCmd( setattrfunc, self, curdata )
 293
			op.doIt()
 295
		finalWrappedSetAttr = wrappedSetAttr
 299
	wrappedUndoableSetAttr = undoable( finalWrappedSetAttr )
 300
	if wrappedUndoableSetAttr is finalWrappedSetAttr:
 301
		return setattrfunc
 304
	return wrappedUndoableSetAttr
 307
class MPlug( api.MPlug ):
 308
	"""Patch applying mrv specific functionality to the MPlug. These methods will be
 309
	available through methods with the 'm' prefix.
 311
	Other methods are overridden to allow more pythonic usage of the MPlug class
 312
	if and only if it is not specific to mrv.
 314
	Additionally it provides aliases for all MPlug methods that are getters, but 
 315
	don't start with a 'get'.
 317
	:note: Theoretically the MPlug would satisfy the 'iDagItem' interface, but due 
 318
		to the method prefixes, it could not work here as it calls un-prefixed methods only."""
 320
	pa = api.MPlugArray( )		# the only way to get a null plug for use
 321
	pa.setLength( 1 )
 325
	def length( self ):
 326
		"""
 327
		:return: number of physical elements in the array, but only if they are 
 328
			not connected. If in doubt, run evaluateNumElements beforehand
 329
		:note: cannot use __len__ as it would break printing of pymel"""
 330
		if not self.isArray( ): return 0
 331
		return self.numElements( )
 333
	def __iter__( self ):
 334
		""":return: iterator object"""
 335
		for i in xrange(self.length()):
 336
			yield self.elementByPhysicalIndex(i)
 338
	__str__ = api.MPlug.name
 340
	def __repr__( self ):
 341
		""":return: our class representation"""
 342
		return "MPlug(%s)" % self.name()
 344
	def __eq__( self, other ):
 345
		"""Compare plugs,handle elements correctly"""
 346
		if not api.MPlug._api___eq__( self, other ):
 347
			return False
 350
		if self.isElement():
 351
			return self.logicalIndex( ) == other.logicalIndex()
 353
		return True
 355
	def __ne__( self, other ):
 356
		return not( self.__eq__( other ) )
 361
	def mparent( self ):
 362
		""":return: parent of this plug or None
 363
		:note: for array plugs, this is the array, for child plugs the actual parent """
 364
		p = None
 365
		if self.isChild():
 366
			p = self.parent()
 367
		elif self.isElement():
 368
			p = self.array()
 370
		if p.isNull( ):	# sanity check - not all
 371
			return None
 372
		return p
 374
	def mchildren( self , predicate = lambda x: True):
 375
		""":return: list of intermediate child plugs, [ plug1 , plug2 ]
 376
		:param predicate: return True to include x in result"""
 377
		outchildren = []
 378
		if self.isCompound():
 379
			nc = self.numChildren()
 380
			for c in xrange( nc ):
 381
				child = self.child( c )
 382
				if predicate( child ):
 383
					outchildren.append( child )
 387
		return outchildren
 389
	def mchildByName( self, childname ):
 390
		""":return: MPlug with the given childname
 391
		:raise AttributeError: if no child plug of the appropriate name could be found
 392
		:raise TypeError: self is not a compound plug"""
 393
		if not self.isCompound( ):
 394
			raise TypeError( "Plug %s is not a compound plug" % self )
 397
		nc = self.numChildren( )
 398
		for c in xrange( nc ):
 399
			child = self.child( c )
 400
			if (	child.partialName( ).split('.')[-1] == childname or
 401
					child.partialName( 0, 0, 0, 0, 0, 1 ).split('.')[-1] == childname ):
 402
				return child
 405
		raise AttributeError( "Plug %s has no child plug called %s" % ( self, childname ) )
 407
	def msubPlugs( self , predicate = lambda x: True):
 408
		"""
 409
		:return: list of intermediate sub-plugs that are either child plugs or element plugs.
 410
			Returned list will be empty for leaf-level plugs
 411
		:param predicate: return True to include x in result
 412
		:note: use this function recursively for easy deep traversal of all
 413
			combinations of array and compound plugs"""
 414
		if self.isCompound( ):
 415
			outchildren = []
 416
			nc = self.numChildren( )
 417
			for c in xrange( nc ):
 418
				child = self.child( c )
 419
				if predicate( child ):
 420
					outchildren.append( child )
 422
			return outchildren
 423
		elif self.isArray( ):
 424
			return [ elm for elm in self ]
 427
		return []
 433
	def _mhandleAttrSet( self, state, getfunc, setfunc ):
 434
		"""Generic attribute handling"""
 435
		op = undo.GenericOperation()
 436
		op.setDoitCmd( setfunc, state )
 437
		op.setUndoitCmd( setfunc, getfunc( ) )
 438
		op.doIt()
 440
	@undoable
 441
	def msetLocked( self, state ):
 442
		"""If True, the plug's value may not be changed anymore"""
 443
		self._mhandleAttrSet( state, self.isLocked, self.setLocked )
 445
	@undoable
 446
	def msetKeyable( self, state ):
 447
		"""if True, the plug may be set using animation curves"""
 448
		self._mhandleAttrSet( state, self.isKeyable, self.setKeyable )
 450
	@undoable
 451
	def msetCaching( self, state ):
 452
		"""if True, the plug's value will be cached, preventing unnecessary computations"""
 453
		self._mhandleAttrSet( state, self.isCachingFlagSet, self.setCaching )
 455
	@undoable
 456
	def msetChannelBox( self, state ):
 457
		"""if True, the plug will be visible in the channelbox, even though it might not
 458
		be keyable or viceversa """
 459
		self._mhandleAttrSet( state, self.isChannelBoxFlagSet, self.setChannelBox )
 466
	@classmethod
 467
	@undoable
 468
	def mconnectMultiToMulti(self, iter_source_destination, force=False):
 469
		"""Connect multiple source plugs to the same amount of detsination plugs.
 471
		:note: This method provides the most efficient way to connect a large known 
 472
			amount of plugs to each other
 473
		:param iter_source_destination: Iterator yielding pairs of source and destination plugs to connect
 474
		:param force: If True, existing input connections on the destination side will 
 475
			be broken automatically. Otherwise the whole operation will fail if one 
 476
			connection could not be made.
 477
		:note: Both iterators need to yield the same total amount of plugs
 478
		:note: In the current implementation, performance will be hurt if force 
 479
			is specified as each destination has to be checked for a connection in advance"""
 480
		mod = undo.DGModifier( )
 481
		for source, dest in iter_source_destination:
 482
			if force:
 483
				destinputplug = dest.minput()
 484
				if not destinputplug.isNull():
 485
					if source == destinputplug:
 486
						continue
 488
					mod.disconnect(destinputplug, dest)
 491
			mod.connect(source, dest)
 493
		mod.doIt()
 494
		return mod
 497
	@undoable
 498
	def mconnectTo( self, destplug, force=True ):
 499
		"""Connect this plug to the right hand side plug
 501
		:param destplug: the plug to which to connect this plug to.
 502
		:param force: if True, the connection will be created even if another connection
 503
			has to be broken to achieve that.
 504
			If False, the connection will fail if destplug is already connected to another plug
 505
		:return: destplug allowing chained connections a.connectTo(b).connectTo(c)
 506
		:raise RuntimeError: If destination is already connected and force = False"""
 507
		mod = undo.DGModifier( )
 511
		if force:
 512
			destinputplug = destplug.minput()
 513
			if not destinputplug.isNull():
 515
				if self == destinputplug:		# is it us already ?
 516
					return destplug
 519
				mod.disconnect( destinputplug, destplug )
 523
		mod.connect( self, destplug )	# finally do the connection
 525
		try:
 526
			mod.doIt( )
 527
		except RuntimeError:
 528
			raise RuntimeError("Failed to connect %s to %s as destination is already connected or incompatible" % (self, destplug))
 530
		return destplug
 532
	@undoable
 533
	def mconnectToArray( self, arrayplug, force = True, exclusive_connection = False ):
 534
		"""Connect self an element of the given arrayplug.
 536
		:param arrayplug: the array plug to which you want to connect to
 537
		:param force: if True, the connection will be created even if another connection
 538
			has to be broken to achieve that.
 539
		:param exclusive_connection: if True and destplug is an array, the plug will only be connected
 540
			to an array element if it is not yet connected
 541
		:return: newly created element plug or the existing one"""
 544
		if arrayplug.isArray( ):
 545
			if exclusive_connection:
 546
				arrayplug.evaluateNumElements( )
 547
				for delm in arrayplug:
 548
					if self == delm.minput():
 549
						return delm
 555
			return self.mconnectTo( arrayplug.mnextLogicalPlug( ), force = force )
 557
		raise AssertionError( "Given plug %r was not an array plug" % arrayplug )
 559
	@undoable
 560
	def mdisconnect( self ):
 561
		"""Completely disconnect all inputs and outputs of this plug. The plug will not 
 562
		be connected anymore.
 564
		:return: self, allowing chained commands"""
 565
		self.mdisconnectInput()
 566
		self.mdisconnectOutputs()
 567
		return self
 569
	@undoable
 570
	def mdisconnectInput( self ):
 571
		"""Disconnect the input connection if one exists
 573
		:return: self, allowing chained commands"""
 574
		inputplug = self.minput()
 575
		if inputplug.isNull():
 576
			return self
 578
		mod = undo.DGModifier( )
 579
		mod.disconnect( inputplug, self )
 580
		mod.doIt()
 581
		return self
 583
	@undoable
 584
	def mdisconnectOutputs( self ):
 585
		"""Disconnect all outgoing connections if they exist
 587
		:return: self, allowing chained commands"""
 588
		outputplugs = self.moutputs()
 589
		if not len( outputplugs ):
 590
			return self
 592
		mod = undo.DGModifier()
 593
		for destplug in outputplugs:
 594
			mod.disconnect( self, destplug )
 595
		mod.doIt()
 596
		return self
 598
	@undoable
 599
	def mdisconnectFrom( self, other ):
 600
		"""Disconnect this plug from other plug if they are connected
 602
		:param other: MPlug that will be disconnected from this plug
 603
		:return: other plug allowing to chain disconnections"""
 604
		try:
 605
			mod = undo.DGModifier( )
 606
			mod.disconnect( self, other )
 607
			mod.doIt()
 608
		except RuntimeError:
 609
			pass
 610
		return other
 612
	@undoable
 613
	def mdisconnectNode( self, other ):
 614
		"""Disconnect this plug from the given node if they are connected
 616
		:param other: Node that will be completely disconnected from this plug"""
 617
		for p in self.moutputs():
 618
			if p.mwrappedNode() == other:
 619
				self.mdisconnectFrom(p)
 626
	@staticmethod
 627
	def mhaveConnection( lhsplug, rhsplug ):
 628
		""":return: True if lhsplug and rhs plug are connected - the direction does not matter
 629
		:note: equals lhsplug & rhsplug"""
 630
		return lhsplug.misConnectedTo( rhsplug ) or rhsplug.misConnectedTo( lhsplug )
 632
	def misConnectedTo( self, destplug ):
 633
		""":return: True if this plug is connected to destination plug ( in that order )
 634
		:note: return true for self.misConnectedTo(destplug) but false for destplug.misConnectedTo(self)
 635
		:note: use the mhaveConnection method whether both plugs have a connection no matter which direction
 636
		:note: use `misConnected` to find out whether this plug is connected at all"""
 637
		return destplug in self.moutputs()
 639
	def moutputs( self ):
 640
		""":return: MPlugArray with all plugs having this plug as source
 641
		:todo: should the method be smarter and deal nicer with complex array or compound plugs ?"""
 642
		outputs = api.MPlugArray()
 643
		self.connectedTo( outputs, False, True )
 644
		return outputs
 646
	def moutput( self ):
 647
		"""
 648
		:return: first plug that has this plug as source of a connection, or null plug 
 649
			if no such plug exists.
 650
		:note: convenience method"""
 651
		outputs = self.moutputs()
 652
		if len( outputs ) == 0:
 653
			return self.pa[0]
 654
		return outputs[0]
 656
	def minput( self ):
 657
		"""
 658
		:return: plug being the source of a connection to this plug or a null plug
 659
			if no such plug exists"""
 660
		inputs = api.MPlugArray()
 661
		self.connectedTo( inputs, True, False )
 663
		noInputs = len( inputs )
 664
		if noInputs == 0:
 666
			return self.pa[0]
 667
		elif noInputs == 1:
 668
			return inputs[0]
 671
		raise ValueError( "Plug %s has more than one input plug - check how that can be" % self )
 673
	def minputs( self ):
 674
		"""Special handler returning the input plugs of array elements
 676
		:return: list of plugs connected to the elements of this arrayplug
 677
		:note: if self is not an array, a list with 1 or 0 plugs will be returned"""
 678
		out = list()
 679
		if self.isArray():
 680
			self.evaluateNumElements()
 681
			for elm in self:
 682
				elminput = elm.minput()
 683
				if elminput.isNull():
 684
					continue
 685
				out.append( elminput )
 687
		else:
 688
			inplug = self.minput()
 689
			if not inplug.isNull():
 690
				out.append( inplug )
 692
		return out
 694
	def miterGraph( self, *args, **kwargs ):
 695
		"""
 696
		:return: graph iterator with self as root, args and kwargs are passed to `it.iterGraph`.
 697
			Plugs are returned by default, but this can be specified explicitly using 
 698
			the plug=True kwarg"""
 699
		import it
 700
		kwargs['plug'] = kwargs.get('plug', True)
 701
		return it.iterGraph(self, *args, **kwargs)
 703
	def miterInputGraph( self, *args, **kwargs ):
 704
		"""
 705
		:return: iterator over the graph starting at this plug in input(upstream) direction.
 706
			Plugs will be returned by default
 707
		:note: see `it.iterGraph` for valid args and kwargs"""
 708
		kwargs['input'] = True
 709
		return self.miterGraph(*args, **kwargs)
 711
	def miterOutputGraph( self, *args, **kwargs ):
 712
		"""
 713
		:return: iterator over the graph starting at this plug in output(downstream) direction.
 714
			Plugs will be returned by default
 715
		:note: see `it.iterGraph` for valid args and kwargs"""
 716
		kwargs['input'] = False
 717
		return self.miterGraph(*args, **kwargs)
 719
	def mconnections( self ):
 720
		""":return: tuple with input and outputs ( inputPlug, outputPlugs )"""
 721
		return ( self.minput( ), self.moutputs( ) )
 726
	def mdependencyInfo( self, by=False ):
 727
		""":return: list of plugs on this node that this plug affects or is being affected by
 728
		:param by: if false, affected attributplugs will be returned, otherwise the attributeplugs affecting this one
 729
		:note: you can also use the `base.DependNode.dependencyInfo` method on the node itself if plugs are not
 730
			required - this will also be faster
 731
		:note: have to use MEL :("""
 732
		ownnode = self.mwrappedNode()
 733
		attrs = cmds.affects( self.mwrappedAttribute().name() , ownnode.name(), by=by ) or list()
 734
		outplugs = list()
 735
		depfn = api.MFnDependencyNode( ownnode.object() )
 737
		for attr in attrs:
 738
			outplugs.append( depfn.findPlug( attr ) )
 739
		return outplugs
 741
	def maffects( self ):
 742
		""":return: list of plugs affected by this one"""
 743
		return self.mdependencyInfo( by = False )
 745
	def maffected( self ):
 746
		""":return: list of plugs affecting this one"""
 747
		return self.mdependencyInfo( by = True )
 752
	def mnextLogicalIndex( self ):
 753
		""":return: index of logical indexed plug that does not yet exist
 754
		:note: as this method does a thorough search, it is relatively slow
 755
			compared to a simple numPlugs + 1 algorithm
 756
		:note: only makes sense for array plugs"""
 757
		indices = api.MIntArray()
 758
		self.getExistingArrayAttributeIndices( indices )
 760
		logicalIndex = 0
 761
		numIndices = indices.length()
 764
		if numIndices == 1:
 765
			logicalIndex =  indices[0] + 1	# just increment the first one
 766
		else:
 768
			for i in xrange( numIndices - 1 ):
 769
				if indices[i+1] - indices[i] > 1:
 770
					logicalIndex = indices[i] + 1 	# at least one free slot here
 771
					break
 772
				else:
 773
					logicalIndex = indices[i+1] + 1	# be always one larger than the last one
 776
		return logicalIndex
 778
	def mnextLogicalPlug( self ):
 779
		""":return: plug at newly created logical index
 780
		:note: only valid for array plugs"""
 781
		return self.elementByLogicalIndex(self.mnextLogicalIndex())
 783
	def mwrappedAttribute( self ):
 784
		""":return: Attribute instance of our underlying attribute"""
 785
		return base.Attribute(self.attribute())
 787
	def mwrappedNode( self ):
 788
		"""
 789
		:return: wrapped Node of the plugs node
 790
		:note: instance information gets lost this way, the respective instance 
 791
			can be re-retrieved using the instance information on this instanced 
 792
			attribute, if this is an instanced attribute"""
 793
		return base.NodeFromObj(self.node())
 795
	def masData( self, *args, **kwargs ):
 796
		""":return: our data Mobject wrapped in `base.Data`
 797
		:note: args and kwagrs have to be provided as MDGContext.fsNormal
 798
			does not exist in maya 8.5, so we have to hide that fact."""
 799
		return base.Data(self.asMObject(*args, **kwargs))
 801
	def mfullyQualifiedName( self ):
 802
		"""
 803
		:return: string returning the absolute and fully qualified name of the
 804
			plug. It might take longer to evaluate but is safe to use if you want to 
 805
			convert the resulting string back to the actual plug"""
 806
		return self.partialName(1, 1, 1, 0, 1, 1)
 814
	msetBool = _mplug_createUndoSetFunc( "Bool" )
 815
	msetChar = _mplug_createUndoSetFunc( "Char" )
 816
	msetShort = _mplug_createUndoSetFunc( "Short" )
 817
	msetInt = _mplug_createUndoSetFunc( "Int" )
 818
	msetFloat = _mplug_createUndoSetFunc( "Float" )
 819
	msetDouble = _mplug_createUndoSetFunc( "Double" )
 820
	msetString = _mplug_createUndoSetFunc( "String" )
 821
	msetMAngle = _mplug_createUndoSetFunc( "MAngle" )
 822
	msetMDistance = _mplug_createUndoSetFunc( "MDistance" )
 823
	msetMTime = _mplug_createUndoSetFunc( "MTime" )
 824
	msetMObject = _mplug_createUndoSetFunc( "MObject" )
 829
	mctf = lambda self,other: self.mconnectTo( other, force=True )
 830
	mct = lambda self,other: self.mconnectTo( other, force=False )
 831
	mict = misConnectedTo
 832
	mhc = lambda lhs,rhs: MPlug.mhaveConnection( lhs, rhs )
 833
	mdc = mdisconnectFrom
 834
	mwn = mwrappedNode
 835
	mwa = mwrappedAttribute
 840
if int(os.environ.get('MRV_DEBUG_MPLUG_SETX', 0)):
 841
	def __getattribute__(self, attr):
 842
		"""Get attribute for MPlug which will raise if a setX method is used.
 843
		This could cause undo bugs that you'd better catch before they hit the user"""
 844
		if attr.startswith('set'):
 845
			raise AssertionError("%s method called on MPlug - this causes undo-issues if it happens unintended" % attr)
 846
		return api.MPlug._api___getattribute__(self, attr)
 850
	MPlug.__getattribute__ = __getattribute__
 859
class ArrayBase( Abstract ):
 860
	""" Base class for all maya arrays to easily fix them
 862
	:note: set _apicls class variable to your api base class """
 864
	def __len__( self ):
 865
		return self._apicls.length( self )
 867
	def __setitem__ ( self, index, item ):
 868
		""":note: does not work as it expects a pointer type - probably a bug"""
 869
		return self.set( item, index )
 871
	@classmethod
 872
	def mfromMultiple(cls, *args):
 873
		""":return: Array created from the given elements"""
 874
		ia = cls()
 875
		ia.setLength(len(args))
 877
		ci = 0
 878
		for elm in args:
 879
			ia[ci] = elm
 880
			ci += 1
 883
		return ia
 885
	@classmethod
 886
	def mfromIter(cls, iter):
 887
		""":return: Array created from elements yielded by iter
 888
		:note: this one is less efficient than `mfromList` as the final length 
 889
			of the array is not predetermined"""
 890
		ia = cls()
 891
		append = ia.append
 892
		for index in iter:
 893
			append(index)
 894
		return ia
 896
	@classmethod
 897
	def mfromList(cls, list):
 898
		""":return: Array created from the given list of elements"""
 899
		ia = cls()
 900
		ia.setLength(len(list))
 902
		ci = 0
 903
		set = ia.set
 904
		for elm in list:
 905
			set(elm, ci)
 906
			ci += 1
 909
		return ia
 912
_plugarray_getitem = api.MPlugArray.__getitem__
 913
_objectarray_getitem = api.MObjectArray.__getitem__
 914
_colorarray_getitem = api.MColorArray.__getitem__
 915
_pointarray_getitem = api.MPointArray.__getitem__
 916
_floatpointarray_getitem = api.MFloatPointArray.__getitem__
 917
_doublearray_getitem = api.MDoubleArray.__getitem__
 918
_floatarray_getitem = api.MFloatArray.__getitem__
 919
_floatvectorarray_getitem = api.MFloatVectorArray.__getitem__
 920
_vectorarray_getitem = api.MVectorArray.__getitem__
 921
class MPlugArray( api.MPlugArray, ArrayBase ):
 922
	""" Wrap MPlugArray to make it compatible to pythonic contructs
 924
	:note: for performance reasons, we do not provide negative index support"""
 925
	_apicls = api.MPlugArray
 927
	def __iter__( self ):
 928
		""":return: iterator object"""
 929
		for i in xrange(len(self)):
 930
			yield api.MPlug(_plugarray_getitem( self,  i ))
 932
	def __getitem__ ( self, index ):
 933
		"""Copy the MPlugs we return to assure their ref count gets incremented"""
 934
		return api.MPlug(_plugarray_getitem( self,  index ))
 937
class MObjectArray( api.MObjectArray, ArrayBase ):
 938
	""" Wrap MObject to make it compatible to pythonic contructs.
 940
	:note: This array also fixes an inherent issue that comes into play when 
 941
		MObjects are returned using __getitem__, as the reference count does not natively
 942
		get incremented, and the MObjects will be obsolete once the parent-array goes out 
 943
		of scope
 944
	:note: for performance reasons, we do not provide negative index support"""
 945
	_apicls = api.MObjectArray
 947
	def __iter__( self ):
 948
		""":return: iterator object"""
 949
		for i in xrange(len(self)):
 950
			yield api.MObject(_objectarray_getitem( self,  i ))
 952
	def __getitem__ ( self, index ):
 953
		"""Copy the MObjects we return to assure their ref count gets incremented"""
 954
		return api.MObject(_objectarray_getitem( self,  index ))
 957
class MColorArray( api.MColorArray, ArrayBase ):
 958
	""" Wrap MColor to make it compatible to pythonic contructs.
 960
	:note: for performance reasons, we do not provide negative index support"""
 961
	_apicls = api.MColorArray
 963
	def __iter__( self ):
 964
		""":return: iterator object"""
 965
		for i in xrange(len(self)):
 966
			yield _colorarray_getitem( self,  i )
 969
class MPointArray( api.MPointArray, ArrayBase ):
 970
	""" Wrap MPoint to make it compatible to pythonic contructs.
 972
	:note: for performance reasons, we do not provide negative index support"""
 973
	_apicls = api.MPointArray
 975
	def __iter__( self ):
 976
		""":return: iterator object"""
 977
		for i in xrange(len(self)):
 978
			yield _pointarray_getitem( self,  i )
 981
class MFloatVectorArray( api.MFloatVectorArray, ArrayBase ):
 982
	""" Wrap MFloatVector to make it compatible to pythonic contructs.
 984
	:note: for performance reasons, we do not provide negative index support"""
 985
	_apicls = api.MFloatVectorArray
 987
	def __iter__( self ):
 988
		""":return: iterator object"""
 989
		for i in xrange(len(self)):
 990
			yield _floatvectorarray_getitem( self,  i )
 993
class MVectorArray( api.MVectorArray, ArrayBase ):
 994
	""":note: for performance reasons, we do not provide negative index support"""
 995
	_apicls = api.MVectorArray
 997
	def __iter__( self ):
 998
		""":return: iterator object"""
 999
		for i in xrange(len(self)):
1000
			yield _vectorarray_getitem( self,  i )
1003
class MFloatPointArray( api.MFloatPointArray, ArrayBase ):
1004
	""" Wrap MFloatPoint to make it compatible to pythonic contructs.
1006
	:note: for performance reasons, we do not provide negative index support"""
1007
	_apicls = api.MFloatPointArray
1009
	def __iter__( self ):
1010
		""":return: iterator object"""
1011
		for i in xrange(len(self)):
1012
			yield _floatpointarray_getitem( self,  i )
1015
class MDoubleArray( api.MDoubleArray, ArrayBase ):
1016
	""":note: for performance reasons, we do not provide negative index support"""
1017
	_apicls = api.MDoubleArray
1019
	def __iter__( self ):
1020
		""":return: iterator object"""
1021
		for i in xrange(len(self)):
1022
			yield _doublearray_getitem( self,  i )
1025
class MFloatArray( api.MFloatArray, ArrayBase ):
1026
	""":note: for performance reasons, we do not provide negative index support"""
1027
	_apicls = api.MFloatArray
1029
	def __iter__( self ):
1030
		""":return: iterator object"""
1031
		for i in xrange(len(self)):
1032
			yield _floatarray_getitem( self,  i )
1035
class MIntArray( api.MIntArray, ArrayBase ):
1036
	"""Attach additional creator functions"""
1037
	_apicls = api.MIntArray
1039
	@classmethod
1040
	def mfromRange(cls, i, j):
1041
		""":return: An MIntArray initialized with integers ranging from i to j
1042
		:param i: first integer of the returned array
1043
		:param j: last integer of returned array will have the value j-1"""
1044
		if j < i:
1045
			raise ValueError("j < i violated")
1046
		if j < 0 or i < 0:
1047
			raise ValueError("negative ranges are not supported")
1049
		ia = api.MIntArray()
1050
		l = j - i
1051
		ia.setLength(l)
1054
		ci = 0
1055
		set = ia.set
1056
		for i in xrange(i, j):
1057
			set(i, ci)
1058
			ci += 1
1066
		return ia
1069
class MSelectionList( api.MSelectionList, ArrayBase ):
1070
	_apicls = api.MSelectionList
1072
	def mhasItem( self, rhs ):
1073
		""":return: True if we contain rhs
1074
		:note: As we check for Nodes as well as MayaAPI objects, we are possibly slow"""
1075
		if isinstance(rhs, base.DagNode):
1076
			return self.hasItem(rhs.dagPath())
1077
		elif isinstance(rhs, base.DependNode):
1078
			return self.hasItem(rhs.object())
1079
		else:
1080
			return self.hasItem(rhs)
1083
	@staticmethod
1084
	def mfromStrings( iter_strings, **kwargs ):
1085
		""":return: MSelectionList initialized from the given iterable of strings
1086
		:param kwargs: passed to `base.toSelectionListFromNames`"""
1087
		return base.toSelectionListFromNames(iter_strings, **kwargs)
1089
	@staticmethod
1090
	def mfromList( iter_items, **kwargs ):
1091
		"""
1092
		:return: MSelectionList as initialized from the given iterable of Nodes, 
1093
			MObjects, MDagPaths, MPlugs or strings
1094
		:param kwargs: passed to `base.toSelectionList`"""
1095
		return base.toSelectionList(iter_items, **kwargs)
1098
	mfromIter = mfromList
1100
	@staticmethod
1101
	def mfromMultiple( *args, **kwargs ):
1102
		"""Alternative form of `mfromList` as args can be passed in."""
1103
		return MSelectionList.mfromList(args, **kwargs)
1105
	@staticmethod
1106
	def mfromComponentList( iter_components, **kwargs ):
1107
		"""
1108
		:return: MSelectionList as initialized from the given list of tuple( DagNode, Component ), 
1109
			Component can be a filled Component object or null MObject
1110
		:param kwargs: passed to `base.toComponentSelectionList`"""
1111
		return base.toComponentSelectionList(iter_components, **kwargs)
1113
	def mtoList( self, *args, **kwargs ):
1114
		""":return: list with the contents of this MSelectionList
1115
		:note: all args and kwargs passed to `it.iterSelectionList`"""
1116
		return list(self.mtoIter(*args, **kwargs))
1118
	def mtoIter( self, *args, **kwargs ):
1119
		""":return: iterator yielding of Nodes and MPlugs stored in this given selection list
1120
		:note: all args and kwargs are passed to `it.iterSelectionList`"""
1121
		return it.iterSelectionList( self, *args, **kwargs )
1123
	def miterComponents( self, **kwargs ):
1124
		"""
1125
		:return: Iterator yielding node, component pairs, component is guaranteed 
1126
			to carry a component, implying that this iterator applies a filter
1127
		:param kwargs: passed on to `it.iterSelectionList`"""
1128
		kwargs['handleComponents'] = True
1129
		pred = lambda pair: not pair[1].isNull()
1130
		kwargs['predicate'] = pred
1131
		return it.iterSelectionList( self, **kwargs )
1133
	def miterPlugs( self, **kwargs ):
1134
		""":return: Iterator yielding all plugs on this selection list.
1135
		:param kwargs: passed on to `it.iterSelectionList`"""
1136
		kwargs['handlePlugs'] = True
1137
		pred = lambda n: isinstance(n, api.MPlug)
1138
		kwargs['predicate'] = pred
1139
		return it.iterSelectionList( self, **kwargs )
1142
class MeshIteratorBase( Abstract ):
1143
	"""Provides common functionality for all MItMesh classes"""
1145
	def __iter__(self):
1146
		""":return: Iterator yielding self for each item in the iteration
1147
		:note: the iteration will be reset before beginning it
1148
		:note: extract the information you are interested in yourself"""
1149
		self.reset()
1150
		next = self.next
1151
		if hasattr(self, 'count'):
1152
			for i in xrange(self.count()):
1153
				yield self
1154
				next()
1156
		else:
1157
			isDone = self.isDone
1158
			while not isDone():
1159
				yield self
1160
				next()
1164
class MItMeshVertex( api.MItMeshVertex, MeshIteratorBase ):
1165
	pass
1167
class MItMeshEdge( api.MItMeshEdge, MeshIteratorBase ):
1168
	pass
1170
class MItMeshPolygon( api.MItMeshPolygon, MeshIteratorBase ):
1171
	pass
1173
class MItMeshFaceVertex( api.MItMeshFaceVertex, MeshIteratorBase ):
1174
	pass