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

Source Code for Module mrv.maya.nt.apipatch

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3  Contains patch classes that are altering their respective api classes 
   4   
   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. 
   8   
   9  As they are usually derived from the class they patch , they could also be used directly 
  10   
  11  :note: **never import classes directly in here**, import the module instead, thus 
  12          **not**: thisImportedClass **but**: module.thisImportedClass ! 
  13  """ 
  14  __docformat__ = "restructuredtext" 
  15   
  16  import base 
  17  import mrv.maya.undo as undo 
  18  import mrv.util as util 
  19  from mrv.interface import iDagItem 
  20   
  21  import maya.OpenMaya as api 
  22  import maya.cmds as cmds 
  23   
  24  import inspect 
  25  import itertools 
  26  import it 
  27  import os 
28 29 # Doesnt need all as it is just a utility package containing patches that are applies 30 # to API classes 31 # __all__ 32 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 39 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 42 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)) 49 50 ns = None 51 if apply_globally: 52 ns = 'm' 53 # END configure namespace mode 54 55 56 for cls in classes: 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: ] ) 61 62 # assure that the actual class rules over methods from lower base classes 63 # by applying them last 64 templateclasses.reverse() 65 66 # skip abstract classes ? 67 if cls is Abstract or cls.__bases__[0] is Abstract: 68 continue 69 70 apicls = cls.__bases__[0] 71 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( ): 76 continue 77 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 84 pass
85
86 87 -class Abstract:
88 """Class flagging that subclasses should be abstract and are only to be used 89 as superclass """ 90 pass
91
92 93 #{ Primitive Types 94 -class TimeDistanceAngleBase( Abstract ):
95 """Base patch class for all indicated classes 96 97 :note: idea for patches from pymel"""
98 - def __str__( self ): return str(float(self))
99 - def __int__( self ): return int(float(self))
100 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())
105 else:
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) )
109
110 111 -class MTime( api.MTime, TimeDistanceAngleBase ) :
112 pass
113
114 -class MDistance( api.MDistance, TimeDistanceAngleBase ) :
115 pass
116
117 -class MAngle( api.MAngle, TimeDistanceAngleBase ) :
118 pass
119
120 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""" 124 @classmethod
125 - def _applyPatch( cls ):
126 """Read per-class values from self and create appropriate methods and 127 set them as well 128 129 :note: idea from pymel""" 130 def __len__(self): 131 """ Number of components in Maya api iterable """ 132 return self._length
133 # END __len__ 134 type.__setattr__( cls.__bases__[0], '__len__', __len__ ) 135 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 )
140 # END __iter__ 141 type.__setattr__( cls.__bases__[0], '__iter__', __iter__) 142 143 def __str__( self ): 144 return "[ %s ]" % " ".join( str( f ) for f in self ) 145 # END __str__ 146 147 type.__setattr__( cls.__bases__[0], '__str__', __str__) 148 149 def __repr__( self ): 150 return "%s([ %s ])" % (type(self).__name__, " ".join( str( f ) for f in self )) 151 # END __str__ 152 153 type.__setattr__( cls.__bases__[0], '__repr__', __repr__) 154 155 # allow the class members to be used ( required as we are using them ) 156 return True 157
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 ) ]
169 # END __iter__ 170 type.__setattr__( cls.__bases__[0], '__iter__', __iter__ ) 171 172 173 def __str__( self ): 174 return "\n".join( str( v ) for v in self )
175 # END __str__ 176 177 type.__setattr__( cls.__bases__[0], '__str__', __str__) 178 179 return True 180
181 182 183 -class MVector( api.MVector, PatchIterablePrimitives ):
184 _length =3
185
186 -class MFloatVector( api.MFloatVector, PatchIterablePrimitives ):
187 _length =3
188
189 -class MPoint( api.MPoint, PatchIterablePrimitives ):
190 _length =4
191
192 -class MFloatPoint( api.MFloatPoint, PatchIterablePrimitives ):
193 _length =4
194
195 -class MColor( api.MColor, PatchIterablePrimitives ):
196 _length =4
197
198 -class MQuaternion( api.MQuaternion, PatchIterablePrimitives ):
199 _length =4
200
201 -class MEulerRotation( api.MEulerRotation, PatchIterablePrimitives ):
202 _length =4
203
204 -class MMatrix( api.MMatrix, PatchMatrix ):
205 _length =4 206 scriptutil = api.MScriptUtil.getDoubleArrayItem
207
208 -class MFloatMatrix( api.MFloatMatrix, PatchMatrix ):
209 _length =4 210 scriptutil = api.MScriptUtil.getFloatArrayItem
211
212 -class MTransformationMatrix( api.MTransformationMatrix, PatchMatrix ):
213 _length =4 214 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__()
222 # END __iter__ 223 type.__setattr__( cls.__bases__[0], '__iter__', __iter__ ) 224 return True
225
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) ) )
232
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 )
238
239 - def getTranslation( self, space = api.MSpace.kTransform ):
240 """This patch is fully compatible to the default method""" 241 return self._api_getTranslation( space )
242
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 )
246
247 #} END primitve types 248 249 #{ Basic Types 250 251 -def _mplug_createUndoSetFunc( dataTypeId, getattroverride = None ):
252 """Create a function setting a value with undo support 253 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)""" 259 # this binds the original setattr and getattr, not the patched one 260 getattrfunc = getattroverride 261 if not getattrfunc: 262 getattrfunc = getattr( api.MPlug, "as"+dataTypeId ) 263 setattrfunc = getattr( api.MPlug, "set"+dataTypeId ) 264 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 ! 272 try: 273 curdata = getattrfunc( self ) 274 except RuntimeError: 275 curdata = api.MObject() 276 op = undo.GenericOperation( ) 277 278 op.setDoitCmd( setattrfunc, self, data ) 279 op.setUndoitCmd( setattrfunc, self, curdata ) 280 281 op.doIt()
282 # END wrapped method 283 finalWrappedSetAttr = wrappedSetAttr 284 else: 285 def wrappedSetAttr( self, data ): 286 # asMObject can fail instead of returning a null object ! 287 curdata = getattrfunc( self ) 288 op = undo.GenericOperation( ) 289 290 op.setDoitCmd( setattrfunc, self, data ) 291 op.setUndoitCmd( setattrfunc, self, curdata ) 292 293 op.doIt() 294 # END wrappedSetAttr method 295 finalWrappedSetAttr = wrappedSetAttr 296 # END MObject special case 297 298 # did undoable do anything ? If not, its disabled and we return the original 299 wrappedUndoableSetAttr = undoable( finalWrappedSetAttr ) 300 if wrappedUndoableSetAttr is finalWrappedSetAttr: 301 return setattrfunc 302 # END return original 303 304 return wrappedUndoableSetAttr 305
306 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. 310 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. 313 314 Additionally it provides aliases for all MPlug methods that are getters, but 315 don't start with a 'get'. 316 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.""" 319 320 pa = api.MPlugArray( ) # the only way to get a null plug for use 321 pa.setLength( 1 ) 322 323 #{ Overridden Methods 324
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( )
332
333 - def __iter__( self ):
334 """:return: iterator object""" 335 for i in xrange(self.length()): 336 yield self.elementByPhysicalIndex(i)
337 338 __str__ = api.MPlug.name 339
340 - def __repr__( self ):
341 """:return: our class representation""" 342 return "MPlug(%s)" % self.name()
343
344 - def __eq__( self, other ):
345 """Compare plugs,handle elements correctly""" 346 if not api.MPlug._api___eq__( self, other ): 347 return False 348 349 # see whether elements are right - both must be elements if one is 350 if self.isElement(): 351 return self.logicalIndex( ) == other.logicalIndex() 352 353 return True
354
355 - def __ne__( self, other ):
356 return not( self.__eq__( other ) )
357 358 #} Overridden Methods 359 360 #{ Plug Hierarchy Query
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() 369 370 if p.isNull( ): # sanity check - not all 371 return None 372 return p
373
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 ) 384 # END FOR EACH CHILD 385 # END if is compound 386 387 return outchildren
388
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 ) 395 # END if is compound 396 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 403 # END if it is the child we look for 404 # END FOR EACH CHILD 405 raise AttributeError( "Plug %s has no child plug called %s" % ( self, childname ) )
406
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 ) 421 # END FOR EACH CHILD 422 return outchildren 423 elif self.isArray( ): 424 return [ elm for elm in self ] 425 426 # we have no sub plugs 427 return []
428 429 #} END hierarcy query 430 431 #{ Attributes ( Edit ) 432
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()
439 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 )
444 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 )
449 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 )
454 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 )
460 461 #} END attributes edit 462 463 464 #{ Connections ( Edit ) 465 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. 470 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 487 # END skip this plug if it is already connected 488 mod.disconnect(destinputplug, dest) 489 # END destination is connected 490 # END handle force 491 mod.connect(source, dest) 492 # END for each source, dest pair 493 mod.doIt() 494 return mod
495 496 497 @undoable
498 - def mconnectTo( self, destplug, force=True ):
499 """Connect this plug to the right hand side plug 500 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( ) 508 509 # is destination already input-connected ? - disconnect it if required 510 # Optimization: We only care if force is specified. It will fail otherwise 511 if force: 512 destinputplug = destplug.minput() 513 if not destinputplug.isNull(): 514 # handle possibly connected plugs 515 if self == destinputplug: # is it us already ? 516 return destplug 517 518 # disconnect 519 mod.disconnect( destinputplug, destplug ) 520 # END disconnect existing 521 # END destination is connected 522 # END force mode 523 mod.connect( self, destplug ) # finally do the connection 524 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)) 529 # END connection failed handling 530 return destplug
531 532 @undoable
533 - def mconnectToArray( self, arrayplug, force = True, exclusive_connection = False ):
534 """Connect self an element of the given arrayplug. 535 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(): 549 return delm 550 # END if self == elm plug 551 # END for each elemnt in destplug 552 # END if exclusive array connection 553 554 # connect the next free plug 555 return self.mconnectTo( arrayplug.mnextLogicalPlug( ), force = force ) 556 # END Array handling 557 raise AssertionError( "Given plug %r was not an array plug" % arrayplug )
558 559 @undoable
560 - def mdisconnect( self ):
561 """Completely disconnect all inputs and outputs of this plug. The plug will not 562 be connected anymore. 563 564 :return: self, allowing chained commands""" 565 self.mdisconnectInput() 566 self.mdisconnectOutputs() 567 return self
568 569 @undoable
570 - def mdisconnectInput( self ):
571 """Disconnect the input connection if one exists 572 573 :return: self, allowing chained commands""" 574 inputplug = self.minput() 575 if inputplug.isNull(): 576 return self 577 578 mod = undo.DGModifier( ) 579 mod.disconnect( inputplug, self ) 580 mod.doIt() 581 return self
582 583 @undoable
584 - def mdisconnectOutputs( self ):
585 """Disconnect all outgoing connections if they exist 586 587 :return: self, allowing chained commands""" 588 outputplugs = self.moutputs() 589 if not len( outputplugs ): 590 return self 591 592 mod = undo.DGModifier() 593 for destplug in outputplugs: 594 mod.disconnect( self, destplug ) 595 mod.doIt() 596 return self
597 598 @undoable
599 - def mdisconnectFrom( self, other ):
600 """Disconnect this plug from other plug if they are connected 601 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
611 612 @undoable
613 - def mdisconnectNode( self, other ):
614 """Disconnect this plug from the given node if they are connected 615 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 621 622 #} END connections edit 623 624 625 #{ Connections ( Query ) 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 )
631
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()
638
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
645
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]
655
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 ) 662 663 noInputs = len( inputs ) 664 if noInputs == 0: 665 # TODO: find a better way to get a MPlugPtr type that can properly be tested for isNull 666 return self.pa[0] 667 elif noInputs == 1: 668 return inputs[0] 669 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 )
672
673 - def minputs( self ):
674 """Special handler returning the input plugs of array elements 675 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 ) 686 # END for each elm plug in sets 687 else: 688 inplug = self.minput() 689 if not inplug.isNull(): 690 out.append( inplug ) 691 # END array handling 692 return out
693
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)
702
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)
710
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)
718
719 - def mconnections( self ):
720 """:return: tuple with input and outputs ( inputPlug, outputPlugs )""" 721 return ( self.minput( ), self.moutputs( ) )
722 723 #} END connections query 724 725 #{ Affects 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() 734 outplugs = list() 735 depfn = api.MFnDependencyNode( ownnode.object() ) 736 737 for attr in attrs: 738 outplugs.append( depfn.findPlug( attr ) ) 739 return outplugs
740
741 - def maffects( self ):
742 """:return: list of plugs affected by this one""" 743 return self.mdependencyInfo( by = False )
744
745 - def maffected( self ):
746 """:return: list of plugs affecting this one""" 747 return self.mdependencyInfo( by = True )
748 749 #} END affects query 750 751 #{ General Query
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 ) 759 760 logicalIndex = 0 761 numIndices = indices.length() 762 763 # do a proper search 764 if numIndices == 1: 765 logicalIndex = indices[0] + 1 # just increment the first one 766 else: 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 771 break 772 else: 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 776 return logicalIndex
777
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())
782
783 - def mwrappedAttribute( self ):
784 """:return: Attribute instance of our underlying attribute""" 785 return base.Attribute(self.attribute())
786
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())
794
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))
800
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)
807 808 #} END query 809 810 811 #{ Set Data with Undo 812 813 # wrap the methods 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" ) 825 826 #} END set data 827 828 #{ Name Remapping 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
836 #} END name remapping 837 838 839 # SETUP DEBUG MODE ? 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 848 849 # will be transferred onto api.MPlug when the patches are auto-applied. 850 MPlug.__getattribute__ = __getattribute__
851 # END setup debug mode 852 853 854 #} END basic types 855 856 857 #{ Arrays 858 859 -class ArrayBase( Abstract ):
860 """ Base class for all maya arrays to easily fix them 861 862 :note: set _apicls class variable to your api base class """ 863
864 - def __len__( self ):
865 return self._apicls.length( self )
866
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 )
870 871 @classmethod
872 - def mfromMultiple(cls, *args):
873 """:return: Array created from the given elements""" 874 ia = cls() 875 ia.setLength(len(args)) 876 877 ci = 0 878 for elm in args: 879 ia[ci] = elm 880 ci += 1 881 # END for each index 882 883 return ia
884 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
895 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)) 901 902 ci = 0 903 set = ia.set 904 for elm in list: 905 set(elm, ci) 906 ci += 1 907 # END for each item 908 909 return ia
910 911 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 923 924 :note: for performance reasons, we do not provide negative index support""" 925 _apicls = api.MPlugArray 926
927 - def __iter__( self ):
928 """:return: iterator object""" 929 for i in xrange(len(self)): 930 yield api.MPlug(_plugarray_getitem( self, i ))
931
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 ))
935
936 937 -class MObjectArray( api.MObjectArray, ArrayBase ):
938 """ Wrap MObject to make it compatible to pythonic contructs. 939 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 946
947 - def __iter__( self ):
948 """:return: iterator object""" 949 for i in xrange(len(self)): 950 yield api.MObject(_objectarray_getitem( self, i ))
951
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 ))
955
956 957 -class MColorArray( api.MColorArray, ArrayBase ):
958 """ Wrap MColor to make it compatible to pythonic contructs. 959 960 :note: for performance reasons, we do not provide negative index support""" 961 _apicls = api.MColorArray 962
963 - def __iter__( self ):
964 """:return: iterator object""" 965 for i in xrange(len(self)): 966 yield _colorarray_getitem( self, i )
967
968 969 -class MPointArray( api.MPointArray, ArrayBase ):
970 """ Wrap MPoint to make it compatible to pythonic contructs. 971 972 :note: for performance reasons, we do not provide negative index support""" 973 _apicls = api.MPointArray 974
975 - def __iter__( self ):
976 """:return: iterator object""" 977 for i in xrange(len(self)): 978 yield _pointarray_getitem( self, i )
979
980 981 -class MFloatVectorArray( api.MFloatVectorArray, ArrayBase ):
982 """ Wrap MFloatVector to make it compatible to pythonic contructs. 983 984 :note: for performance reasons, we do not provide negative index support""" 985 _apicls = api.MFloatVectorArray 986
987 - def __iter__( self ):
988 """:return: iterator object""" 989 for i in xrange(len(self)): 990 yield _floatvectorarray_getitem( self, i )
991
992 993 -class MVectorArray( api.MVectorArray, ArrayBase ):
994 """:note: for performance reasons, we do not provide negative index support""" 995 _apicls = api.MVectorArray 996
997 - def __iter__( self ):
998 """:return: iterator object""" 999 for i in xrange(len(self)): 1000 yield _vectorarray_getitem( self, i )
1001
1002 1003 -class MFloatPointArray( api.MFloatPointArray, ArrayBase ):
1004 """ Wrap MFloatPoint to make it compatible to pythonic contructs. 1005 1006 :note: for performance reasons, we do not provide negative index support""" 1007 _apicls = api.MFloatPointArray 1008
1009 - def __iter__( self ):
1010 """:return: iterator object""" 1011 for i in xrange(len(self)): 1012 yield _floatpointarray_getitem( self, i )
1013
1014 1015 -class MDoubleArray( api.MDoubleArray, ArrayBase ):
1016 """:note: for performance reasons, we do not provide negative index support""" 1017 _apicls = api.MDoubleArray 1018
1019 - def __iter__( self ):
1020 """:return: iterator object""" 1021 for i in xrange(len(self)): 1022 yield _doublearray_getitem( self, i )
1023
1024 1025 -class MFloatArray( api.MFloatArray, ArrayBase ):
1026 """:note: for performance reasons, we do not provide negative index support""" 1027 _apicls = api.MFloatArray 1028
1029 - def __iter__( self ):
1030 """:return: iterator object""" 1031 for i in xrange(len(self)): 1032 yield _floatarray_getitem( self, i )
1033
1034 1035 -class MIntArray( api.MIntArray, ArrayBase ):
1036 """Attach additional creator functions""" 1037 _apicls = api.MIntArray 1038 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") 1048 1049 ia = api.MIntArray() 1050 l = j - i 1051 ia.setLength(l) 1052 1053 # wouldn't it be great to have a real for loop now ? 1054 ci = 0 1055 set = ia.set 1056 for i in xrange(i, j): 1057 set(i, ci) 1058 ci += 1 1059 # END for each integer 1060 1061 # this is slightly slower 1062 #for ci, i in enumerate(xrange(i, j)): 1063 # ia[ci] = i 1064 # END for each index/value pair 1065 1066 return ia
1067
1068 1069 -class MSelectionList( api.MSelectionList, ArrayBase ):
1070 _apicls = api.MSelectionList 1071
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)
1081 # END handle input type 1082 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)
1088 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)
1096 1097 # We need to override the respective method on the base class as it wouldnt work 1098 mfromIter = mfromList 1099 1100 @staticmethod
1101 - def mfromMultiple( *args, **kwargs ):
1102 """Alternative form of `mfromList` as args can be passed in.""" 1103 return MSelectionList.mfromList(args, **kwargs)
1104 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)
1112
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))
1117
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 )
1122
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 )
1132
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 )
1140
1141 1142 -class MeshIteratorBase( Abstract ):
1143 """Provides common functionality for all MItMesh classes""" 1144
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() 1155 # END for each item 1156 else: 1157 isDone = self.isDone 1158 while not isDone(): 1159 yield self 1160 next()
1161 # END while we have items
1162 # END handle optimized iteration, saving function calls 1163 1164 -class MItMeshVertex( api.MItMeshVertex, MeshIteratorBase ):
1165 pass
1166
1167 -class MItMeshEdge( api.MItMeshEdge, MeshIteratorBase ):
1168 pass
1169
1170 -class MItMeshPolygon( api.MItMeshPolygon, MeshIteratorBase ):
1171 pass
1172
1173 -class MItMeshFaceVertex( api.MItMeshFaceVertex, MeshIteratorBase ):
1174 pass
1175 #} 1176