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
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 !
14
__docformat__ = "restructuredtext"
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
29
# Doesnt need all as it is just a utility package containing patches that are applies
33
def init_applyPatches( ):
34
"""Called by package __init__ method to finally apply the patch according to
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))
53
# END configure namespace mode
57
# use the main class as well as all following base
58
# the first base is always the main maya type that is patched - we skip it
59
templateclasses = [ cls ]
60
templateclasses.extend( cls.__bases__[ 1: ] )
62
# assure that the actual class rules over methods from lower base classes
63
# by applying them last
64
templateclasses.reverse()
66
# skip abstract classes ?
67
if cls is Abstract or cls.__bases__[0] is Abstract:
70
apicls = cls.__bases__[0]
72
# SPECIAL CALL INTERFACE ?
73
# If so, call and let the class do the rest
74
if hasattr( cls, "_applyPatch" ):
75
if not cls._applyPatch( ):
78
for tplcls in templateclasses:
79
util.copyClsMembers( tplcls, apicls, overwritePrefix="_api_",
80
forbiddenMembers = forbiddenMembers,
81
copyNamespaceGlobally=ns)
82
# END for each template class
83
# END for each cls of this module
88
"""Class flagging that subclasses should be abstract and are only to be used
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))
101
# in Maya 2010, these classes have an as_units method allowing
102
# it to be used in python without the use of getattr
103
if hasattr(api.MTime, 'asUnits'):
104
def __float__( self ): return self.asUnits(self.uiUnit())
106
def __float__( self ): return getattr(self, 'as')(self.uiUnit())
107
# END conditional implementation
108
def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, float(self) )
111
class MTime( api.MTime, TimeDistanceAngleBase ) :
114
class MDistance( api.MDistance, TimeDistanceAngleBase ) :
117
class MAngle( api.MAngle, TimeDistanceAngleBase ) :
121
# patch some Maya api classes that miss __iter__ to make them iterable / convertible to list
122
class PatchIterablePrimitives( Abstract ):
123
""":note: Classes derived from this base should not be used directly"""
125
def _applyPatch( cls ):
126
"""Read per-class values from self and create appropriate methods and
129
:note: idea from pymel"""
131
""" Number of components in Maya api iterable """
134
type.__setattr__( cls.__bases__[0], '__len__', __len__ )
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__)
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__)
155
# allow the class members to be used ( required as we are using them )
158
class PatchMatrix( Abstract, PatchIterablePrimitives ):
159
"""Only for matrices"""
161
def _applyPatch( cls ):
162
"""Special version for matrices"""
163
PatchIterablePrimitives._applyPatch.im_func( cls )
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__ )
174
return "\n".join( str( v ) for v in self )
177
type.__setattr__( cls.__bases__[0], '__str__', __str__)
183
class MVector( api.MVector, PatchIterablePrimitives ):
186
class MFloatVector( api.MFloatVector, PatchIterablePrimitives ):
189
class MPoint( api.MPoint, PatchIterablePrimitives ):
192
class MFloatPoint( api.MFloatPoint, PatchIterablePrimitives ):
195
class MColor( api.MColor, PatchIterablePrimitives ):
198
class MQuaternion( api.MQuaternion, PatchIterablePrimitives ):
201
class MEulerRotation( api.MEulerRotation, PatchIterablePrimitives ):
204
class MMatrix( api.MMatrix, PatchMatrix ):
206
scriptutil = api.MScriptUtil.getDoubleArrayItem
208
class MFloatMatrix( api.MFloatMatrix, PatchMatrix ):
210
scriptutil = api.MScriptUtil.getFloatArrayItem
212
class MTransformationMatrix( api.MTransformationMatrix, PatchMatrix ):
216
def _applyPatch( cls ):
217
"""Special version for matrices"""
218
PatchMatrix._applyPatch.im_func( cls )
220
""" Iterates on all 4 rows of a Maya api MMatrix """
221
return self.asMatrix().__iter__()
223
type.__setattr__( cls.__bases__[0], '__iter__', __iter__ )
226
def mgetScale( self , space = api.MSpace.kTransform ):
227
ms = api.MScriptUtil()
228
ms.createFromDouble( 1.0, 1.0, 1.0 )
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 )
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
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)"""
259
# this binds the original setattr and getattr, not the patched one
260
getattrfunc = getattroverride
262
getattrfunc = getattr( api.MPlug, "as"+dataTypeId )
263
setattrfunc = getattr( api.MPlug, "set"+dataTypeId )
265
# YES, WE DUPLICATE CODE FOR SPEED
266
####################################
267
# Create actual functions
268
finalWrappedSetAttr = None
269
if dataTypeId == "MObject":
270
def wrappedSetAttr( self, data ):
271
# asMObject can fail instead of returning a null object !
273
curdata = getattrfunc( self )
275
curdata = api.MObject()
276
op = undo.GenericOperation( )
278
op.setDoitCmd( setattrfunc, self, data )
279
op.setUndoitCmd( setattrfunc, self, curdata )
283
finalWrappedSetAttr = wrappedSetAttr
285
def wrappedSetAttr( self, data ):
286
# asMObject can fail instead of returning a null object !
287
curdata = getattrfunc( self )
288
op = undo.GenericOperation( )
290
op.setDoitCmd( setattrfunc, self, data )
291
op.setUndoitCmd( setattrfunc, self, curdata )
294
# END wrappedSetAttr method
295
finalWrappedSetAttr = wrappedSetAttr
296
# END MObject special case
298
# did undoable do anything ? If not, its disabled and we return the original
299
wrappedUndoableSetAttr = undoable( finalWrappedSetAttr )
300
if wrappedUndoableSetAttr is finalWrappedSetAttr:
302
# END return original
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
323
#{ Overridden Methods
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 ):
349
# see whether elements are right - both must be elements if one is
351
return self.logicalIndex( ) == other.logicalIndex()
355
def __ne__( self, other ):
356
return not( self.__eq__( other ) )
358
#} Overridden Methods
360
#{ Plug Hierarchy Query
362
""":return: parent of this plug or None
363
:note: for array plugs, this is the array, for child plugs the actual parent """
367
elif self.isElement():
370
if p.isNull( ): # sanity check - not all
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"""
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 )
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 ):
403
# END if it is the child we look for
405
raise AttributeError( "Plug %s has no child plug called %s" % ( self, childname ) )
407
def msubPlugs( self , predicate = lambda x: True):
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( ):
416
nc = self.numChildren( )
417
for c in xrange( nc ):
418
child = self.child( c )
419
if predicate( child ):
420
outchildren.append( child )
423
elif self.isArray( ):
424
return [ elm for elm in self ]
426
# we have no sub plugs
429
#} END hierarcy query
431
#{ Attributes ( Edit )
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( ) )
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 )
446
def msetKeyable( self, state ):
447
"""if True, the plug may be set using animation curves"""
448
self._mhandleAttrSet( state, self.isKeyable, self.setKeyable )
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 )
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 )
461
#} END attributes edit
464
#{ Connections ( Edit )
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:
483
destinputplug = dest.minput()
484
if not destinputplug.isNull():
485
if source == destinputplug:
487
# END skip this plug if it is already connected
488
mod.disconnect(destinputplug, dest)
489
# END destination is connected
491
mod.connect(source, dest)
492
# END for each source, dest pair
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( )
509
# is destination already input-connected ? - disconnect it if required
510
# Optimization: We only care if force is specified. It will fail otherwise
512
destinputplug = destplug.minput()
513
if not destinputplug.isNull():
514
# handle possibly connected plugs
515
if self == destinputplug: # is it us already ?
519
mod.disconnect( destinputplug, destplug )
520
# END disconnect existing
521
# END destination is connected
523
mod.connect( self, destplug ) # finally do the connection
528
raise RuntimeError("Failed to connect %s to %s as destination is already connected or incompatible" % (self, destplug))
529
# END connection failed handling
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"""
542
# ARRAY PLUG HANDLING
543
######################
544
if arrayplug.isArray( ):
545
if exclusive_connection:
546
arrayplug.evaluateNumElements( )
547
for delm in arrayplug:
548
if self == delm.minput():
550
# END if self == elm plug
551
# END for each elemnt in destplug
552
# END if exclusive array connection
554
# connect the next free plug
555
return self.mconnectTo( arrayplug.mnextLogicalPlug( ), force = force )
557
raise AssertionError( "Given plug %r was not an array plug" % arrayplug )
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()
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():
578
mod = undo.DGModifier( )
579
mod.disconnect( inputplug, self )
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 ):
592
mod = undo.DGModifier()
593
for destplug in outputplugs:
594
mod.disconnect( self, destplug )
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"""
605
mod = undo.DGModifier( )
606
mod.disconnect( self, other )
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)
620
# END for each plug in output
622
#} END connections edit
625
#{ Connections ( Query )
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 )
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:
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 )
665
# TODO: find a better way to get a MPlugPtr type that can properly be tested for isNull
670
# must have more than one input - can this ever be ?
671
raise ValueError( "Plug %s has more than one input plug - check how that can be" % 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"""
680
self.evaluateNumElements()
682
elminput = elm.minput()
683
if elminput.isNull():
685
out.append( elminput )
686
# END for each elm plug in sets
688
inplug = self.minput()
689
if not inplug.isNull():
694
def miterGraph( self, *args, **kwargs ):
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"""
700
kwargs['plug'] = kwargs.get('plug', True)
701
return it.iterGraph(self, *args, **kwargs)
703
def miterInputGraph( self, *args, **kwargs ):
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 ):
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( ) )
723
#} END connections query
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()
735
depfn = api.MFnDependencyNode( ownnode.object() )
738
outplugs.append( depfn.findPlug( attr ) )
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 )
761
numIndices = indices.length()
765
logicalIndex = indices[0] + 1 # just increment the first one
767
# assume indices are SORTED, smallest first
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
773
logicalIndex = indices[i+1] + 1 # be always one larger than the last one
774
# END for each logical index
775
# END if more than one indices exist
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 ):
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 ):
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)
811
#{ Set Data with Undo
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
835
mwa = mwrappedAttribute
836
#} END name remapping
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)
847
# END method override
849
# will be transferred onto api.MPlug when the patches are auto-applied.
850
MPlug.__getattribute__ = __getattribute__
851
# END setup debug mode
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 """
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 )
872
def mfromMultiple(cls, *args):
873
""":return: Array created from the given elements"""
875
ia.setLength(len(args))
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"""
897
def mfromList(cls, list):
898
""":return: Array created from the given list of elements"""
900
ia.setLength(len(list))
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
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
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"""
1045
raise ValueError("j < i violated")
1047
raise ValueError("negative ranges are not supported")
1049
ia = api.MIntArray()
1053
# wouldn't it be great to have a real for loop now ?
1056
for i in xrange(i, j):
1059
# END for each integer
1061
# this is slightly slower
1062
#for ci, i in enumerate(xrange(i, j)):
1064
# END for each index/value pair
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())
1080
return self.hasItem(rhs)
1081
# END handle input type
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)
1090
def mfromList( iter_items, **kwargs ):
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)
1097
# We need to override the respective method on the base class as it wouldnt work
1098
mfromIter = mfromList
1101
def mfromMultiple( *args, **kwargs ):
1102
"""Alternative form of `mfromList` as args can be passed in."""
1103
return MSelectionList.mfromList(args, **kwargs)
1106
def mfromComponentList( iter_components, **kwargs ):
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 ):
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"""
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"""
1151
if hasattr(self, 'count'):
1152
for i in xrange(self.count()):
1157
isDone = self.isDone
1161
# END while we have items
1162
# END handle optimized iteration, saving function calls
1164
class MItMeshVertex( api.MItMeshVertex, MeshIteratorBase ):
1167
class MItMeshEdge( api.MItMeshEdge, MeshIteratorBase ):
1170
class MItMeshPolygon( api.MItMeshPolygon, MeshIteratorBase ):
1173
class MItMeshFaceVertex( api.MItMeshFaceVertex, MeshIteratorBase ):