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

Source Code for Module mrv.maya.nt.base

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3  Contains some basic  classes that are required to run the nodes system 
   4   
   5  All classes defined here can replace classes in the node type hierarachy if the name 
   6  matches. This allows to create hand-implemented types. 
   7  """ 
   8  __docformat__ = "restructuredtext" 
   9   
  10  from typ import nodeTypeToMfnClsMap, nodeTypeTree, MetaClassCreatorNodes, _addCustomType 
  11  from mrv.util import uncapitalize, capitalize, pythonIndex, Call  
  12  from mrv.interface import iDuplicatable, iDagItem 
  13  from mrv.maya.util import StandinClass 
  14  import maya.OpenMaya as api 
  15  import maya.cmds as cmds 
  16  import mrv.maya.ns as nsm 
  17  import mrv.maya.undo as undo 
  18  import mrv.maya.env as env 
  19  from new import instancemethod 
  20  from util import in_double3_out_vector, undoable_in_double3_as_vector 
  21  import logging 
  22  log = logging.getLogger("mrv.maya.nt.base") 
  23   
  24  # direct import to safe api. lookup 
  25  from maya.OpenMaya import MFnDagNode, MDagPath, MObject, MObjectHandle 
  26   
  27  from itertools import chain 
  28  import sys 
  29   
  30  _nodesdict = None                               # will be set during maya.nt initialization 
  31   
  32   
  33  __all__ = ("nodeTypeToNodeTypeCls", "isAbsolutePath", "toApiobj", "toApiobjOrDagPath",  
  34             "toSelectionList", "toComponentSelectionList", "toSelectionListFromNames",  
  35             "fromSelectionList", "toNodesFromNames", "findByName", "objExists",  
  36             "delete", "selection", "activeSelectionList", "iterSelection", "select",  
  37             "createNode", "SetFilter", "Node", "NodeFromObj", "NodeFromStr",  
  38             "DependNode", "Entity", "DagNode", "Attribute", "UnitAttribute", "TypedAttribute",  
  39             "NumericAttribute", "MessageAttribute", "MatrixAttribute", "LightDataAttribute",  
  40             "GenericAttribute", "EnumAttribute", "CompoundAttribute", "Data", "VectorArrayData",  
  41             "UInt64ArrayData", "StringData", "StringArrayData", "SphereData", "PointArrayData",  
  42             "PluginData", "NumericData", "NObjectData", "MatrixData", "IntArrayData",  
  43             "GeometryData", "SubdData", "NurbsSurfaceData", "NurbsCurveData", "MeshData",  
  44             "LatticeData", "DynSweptGeometryData", "DoubleArrayData", "ComponentListData",  
  45             "ArrayAttrsData", "Component", "SingleIndexedComponent", "DoubleIndexedComponent",  
  46             "TripleIndexedComponent", "MDagPathUtil", "Reference", "Transform", "Shape") 
  47   
  48   
  49  ############################ 
  50  #### Cache                              #### 
  51  ########################## 
  52  # to prevent always creating instances of the same class per call 
  53  _nameToApiSelList = api.MSelectionList() 
  54  _mfndep = api.MFnDependencyNode() 
  55  _mfndag = api.MFnDagNode() 
  56   
  57  # cache functions 
  58  _mfndep_setobject = _mfndep.setObject 
  59  _mfndag_setObject = _mfndag.setObject 
  60  _mfndep_typename = _mfndep.typeName 
  61  _mfndag_typename = _mfndag.typeName 
  62  _mfndep_name = _mfndep.name 
  63   
  64  _api_mdagpath_node = MDagPath.node 
  65  _apitype_to_name = dict()                       # [int] - > type name string 
  66   
  67  _plugin_type_ids = (    api.MFn.kPluginDeformerNode,  
  68                                                          api.MFn.kPluginDependNode, 
  69                                                          api.MFn.kPluginEmitterNode,  
  70                                                          api.MFn.kPluginFieldNode, 
  71                                                          api.MFn.kPluginHwShaderNode, 
  72                                                          api.MFn.kPluginIkSolver, 
  73                                                          api.MFn.kPluginImagePlaneNode, 
  74                                                          api.MFn.kPluginLocatorNode, 
  75                                                          api.MFn.kPluginManipContainer, 
  76                                                          api.MFn.kPluginObjectSet,  
  77                                                          api.MFn.kPluginParticleAttributeMapperNode,  
  78                                                          api.MFn.kPluginShape, 
  79                                                          api.MFn.kPluginSpringNode, 
  80                                                          api.MFn.kPluginTransformNode) 
  81   
  82  _plugin_type_ids_lut = set(_plugin_type_ids) 
  83   
  84  _plugin_type_to_node_type_name = dict(zip((_plugin_type_ids), ("UnknownPluginDeformerNode",  
  85                                                                                                                                  "UnknownPluginDependNode", 
  86                                                                                                                                  "UnknownPluginEmitterNode",  
  87                                                                                                                                  "UnknownPluginFieldNode", 
  88                                                                                                                                  "UnknownPluginHwShaderNode", 
  89                                                                                                                                  "UnknownPluginIkSolver", 
  90                                                                                                                                  "UnknownPluginImagePlaneNode", 
  91                                                                                                                                  "UnknownPluginLocatorNode", 
  92                                                                                                                                  "UnknownPluginManipContainer", 
  93                                                                                                                                  "UnknownPluginObjectSet",  
  94                                                                                                                                  "UnknownPluginParticleAttributeMapperNode",  
  95                                                                                                                                  "UnknownPluginShape", 
  96                                                                                                                                  "UnknownPluginSpringNode", 
  97                                                                                                                                  "UnknownPluginTransformNode"))) 
98 99 100 101 ############################ 102 #### Methods #### 103 ########################## 104 105 #{ Conversions 106 107 -def nodeTypeToNodeTypeCls(nodeTypeName, apiobj):
108 """ Convert the given node type (str) to the respective python node type class 109 110 :param nodeTypeName: the type name you which to have the actual class for 111 :param apiobj: source api object, its apiType is used as fallback in case we 112 don't know the node""" 113 try: 114 nodeTypeCls = _nodesdict[capitalize(nodeTypeName)] 115 except KeyError: 116 # assume its a plugin node - in that case the parent will be nicely defined 117 # and helps us to figure out that its a default dummy 118 parentclsname = _plugin_type_to_node_type_name.get(apiobj.apiType(), (isinstance(apiobj, MDagPath) and 'UnknownDag') or 'Unknown') 119 _addCustomType(_nodesdict, parentclsname, nodeTypeName) 120 nodeTypeCls = _nodesdict[capitalize(nodeTypeName)] 121 # END exception handling 122 123 if isinstance(nodeTypeCls, StandinClass): 124 nodeTypeCls = nodeTypeCls.createCls() 125 126 return nodeTypeCls
127
128 129 -def _makeAbsolutePath(nodename):
130 # if dag paths are passed in, we do nothing as a dag object is obviously meant. 131 # Otherwise prepend a '|' to make it a dag object - the calling method will deal 132 # with it accordingly 133 if nodename.count('|') == 0: 134 return '|' + nodename 135 return nodename
136
137 -def isAbsolutePath(nodename):
138 return nodename.startswith('|')
139
140 -def toDagPath(apiobj):
141 """Find ONE valid dag path to the given dag api object""" 142 dagpath = MDagPath() 143 MFnDagNode(apiobj).getPath(dagpath) 144 return dagpath
145
146 -def toApiobj(nodename):
147 """ Convert the given nodename to the respective MObject 148 149 :note: uses unique names only, and will fail if a non-unique path is given, which is 150 as selection lists do not work properly with partial names ! 151 :note: even dag objects will end up as MObject 152 :note: code repeats partly in toApiobjOrDagPath as its supposed to be as fast 153 as possible - this method gets called quite a few times in benchmarks""" 154 _nameToApiSelList.clear() 155 156 nodename = _makeAbsolutePath(nodename) 157 158 objnamelist = [nodename] 159 if nodename.startswith("|") and nodename.count('|') == 1: 160 objnamelist.append(nodename[1:]) 161 162 for name in objnamelist: 163 try: # DEPEND NODE ? 164 _nameToApiSelList.add(name) 165 except: 166 continue 167 else: 168 obj = MObject() 169 _nameToApiSelList.getDependNode(0, obj) 170 171 # if we requested a dg node, but got a dag node, fail 172 if name.count('|') == 0 and obj.hasFn(api.MFn.kDagNode): 173 log.warn("Skipped %s as a dependency node was expected, but got a dag node" % name) 174 continue 175 # END dag/dg inconsistency handling 176 177 return obj 178 # END if no exception on selectionList.add 179 # END for each test-object 180 return None
181
182 -def toApiobjOrDagPath(nodename):
183 """Convert the given nodename to the respective MObject or MDagPath 184 185 :note: we treat "nodename" and "\|nodename" as the same objects as they occupy the 186 same namespace - one time a dep node is meant, the other time a dag node. 187 If querying a dag node, the dep node with the same name is not found, although it is in 188 the same freaking namespace ! IMHO this is a big bug !""" 189 _nameToApiSelList.clear() 190 191 nodename = _makeAbsolutePath(nodename) 192 193 objnamelist = [nodename] 194 if nodename.startswith("|") and nodename.count('|') == 1: # check dep node too ! ("|nodename", but "nodename" could exist too, occupying the "|nodename" name ! 195 objnamelist.append(nodename[1:]) 196 197 for name in objnamelist: 198 try: # DEPEND NODE ? 199 _nameToApiSelList.add(name) 200 except: 201 continue 202 else: 203 try: 204 dag = MDagPath() 205 _nameToApiSelList.getDagPath(0 , dag) 206 return dag 207 except RuntimeError: 208 obj = MObject() 209 _nameToApiSelList.getDependNode(0, obj) 210 211 # if we requested a dg node, but got a dag node, fail 212 if name.count('|') == 0 and obj.hasFn(api.MFn.kDagNode): 213 log.warn("Skipped %s as a dependency node was expected, but got a dag node" % name) 214 continue 215 # END dag/dg inconsistency handling 216 217 return obj 218 # END if no exception on selectionList.add 219 # END for each object name 220 return None
221
222 -def toSelectionList(nodeList, mergeWithExisting = False):
223 """Convert an iterable filled with Nodes to a selection list 224 225 :param nodeList: iterable filled with dg and dag nodes as well as plugs, dagpaths or mobjects or strings 226 :param mergeWithExisting: if true, the selection list will not allow dupliacates , but adding objects 227 also takes (much) longer, depending on the size of the list 228 :return: selection list filled with objects from node list""" 229 if isinstance(nodeList, api.MSelectionList): # sanity check 230 return nodeList 231 232 sellist = api.MSelectionList() 233 for node in nodeList: 234 if isinstance(node, DagNode): 235 sellist.add(node.dagPath(), MObject(), mergeWithExisting) 236 elif isinstance(node, DependNode): 237 sellist.add(node.object(), mergeWithExisting) 238 else: # probably plug or something else like an mobject or dagpath 239 # cannot properly apply our flag here without intensive checking 240 # TODO: probably put in the instance checks ! 241 sellist.add(node) 242 # END for each item in input array 243 return sellist
244
245 -def toComponentSelectionList(nodeCompList, mergeWithExisting = False):
246 """As above, but only works on DagNodes having components - the components 247 can be a nullObject though to add the whole object after all. 248 249 :param nodeCompList: list of tuple(DagNode, Component), Component can be 250 filled component or null MObject""" 251 if isinstance(nodeCompList, api.MSelectionList): # sanity check 252 return nodeList 253 254 sellist = api.MSelectionList() 255 for node, component in nodeCompList: 256 sellist.add(node.dagPath(), component, mergeWithExisting) 257 258 return sellist
259
260 -def toSelectionListFromNames(nodenames):
261 """Convert the given iterable of nodenames to a selection list 262 263 :return: MSelectionList, use `iterSelectionList` to retrieve the objects""" 264 sellist = api.MSelectionList() 265 for name in nodenames: 266 sellist.add(name) 267 268 return sellist
269
270 -def fromSelectionList(sellist, handlePlugs=1, **kwargs):
271 """:return: list of Nodes and MPlugs stored in the given selection list 272 :param kwargs: passed to selectionListIterator""" 273 kwargs['asNode'] = 1 274 kwargs['handlePlugs'] = handlePlugs 275 return list(sellist.mtoIter(**kwargs))
276
277 -def toNodesFromNames(nodenames, **kwargs):
278 """:return: list of wrapped nodes from the given list of node names 279 :note: this function is supposed to be faster for multiple nodes compared to 280 just creating a Node directly as we optimize the process due to the intermediate 281 selection list getting the api objects for the given names 282 :param kwargs: passed to `fromSelectionList`""" 283 return fromSelectionList(toSelectionListFromNames(nodenames), **kwargs)
284
285 -def findByName(name , **kwargs):
286 """ 287 :return: list of node matching name, whereas simple regex using ``*`` can be used 288 to describe a pattern 289 :param name: string like pcube, or pcube*, or ``pcube*|*Shape`` 290 :param kwargs: passed to `fromSelectionList`""" 291 sellist = api.MSelectionList() 292 api.MGlobal.getSelectionListByName(name, sellist) 293 294 return fromSelectionList(sellist, **kwargs)
295
296 #} END conversions 297 298 299 300 #{ Base 301 302 -def objExists(objectname):
303 """:return: True if given object exists, false otherwise 304 :param objectname: we always use absolute paths to have a unique name 305 :note: perfer this method over mel as the API is used directly as we have some special 306 handling to assure we get the right nodes""" 307 return toApiobj(objectname) is not None
308
309 310 @undoable 311 -def delete(*args, **kwargs):
312 """Delete the given nodes 313 314 :param args: Node instances, MObjects, MDagPaths or strings to delete 315 :param kwargs: 316 * presort: 317 if True, default False, will do alot of pre-work to actually 318 make the deletion work properly using the UI, thus we sort dag nodes 319 by dag path token length to delete top level ones first and individually, 320 to finally delete all dependency nodes in a bunch 321 322 Using this flag will be slower, but yields much better results if deleting complex 323 dag and dependency trees with locked attributes, conversion nodes, transforms and shapes 324 :note: in general , no matter which options have been chosen , api deletion does not work well 325 as the used algorithm is totally different and inferior to the mel implementaiton 326 :note: will not raise in case of an error, but print a notification message 327 :note: all deletions will be stored on one undo operation""" 328 presort = kwargs.get("presort", False) 329 330 # presort - this allows objects high up in the hierarchy to be deleted first 331 # Otherwise we might have trouble deleting the ones lower in the hierarchy 332 # We are basically reimplementing the MEL command 'delete' which does the 333 # same thing internally I assume 334 nodes = toSelectionList(args).mtoList() 335 if presort: 336 depnodes = list() 337 dagnodes = list() 338 for node in nodes: 339 if isinstance(node, DagNode): 340 dagnodes.append(node) 341 else: 342 depnodes.append(node) 343 # END for each node in nodes for categorizing 344 345 # long paths first 346 dagnodes.sort(key = lambda n: len(str(n).split('|')), reverse = True) 347 348 # use all of these in order 349 nodes = chain(dagnodes, depnodes) 350 # END presorting 351 352 # NOTE: objects really want to be deleted individually - otherwise 353 # maya might just crash for some reason !! 354 for node in nodes: 355 if not node.isValid(): 356 continue 357 358 try: 359 node.delete() 360 except RuntimeError: 361 log.error("Deletion of %s failed" % node)
362 # END exception handling
363 # END for each node to delete 364 365 -def selection(filterType=api.MFn.kInvalid, **kwargs):
366 """:return: list of Nodes from the current selection 367 :param filterType: The type of nodes to return exclusively. Defaults to 368 returning all nodes. 369 :param kwargs: passed to `fromSelectionList`""" 370 kwargs['filterType'] = filterType 371 return fromSelectionList(activeSelectionList(), **kwargs)
372
373 -def activeSelectionList():
374 """:return: MSelectionList of the current selection list""" 375 sellist = api.MSelectionList() 376 api.MGlobal.getActiveSelectionList(sellist) 377 return sellist
378
379 -def iterSelection(filterType=api.MFn.kInvalid, **kwargs):
380 """:return: iterator over current scene selection 381 :param filterType: MFn type specifying the node type to iterate upon. Defaults 382 to all node types. 383 :param kwargs: passed to `it.iterSelectionList` 384 :note: This iterator will always return Nodes""" 385 kwargs['asNode'] = 1 # remove our overridden warg 386 kwargs['filterType'] = filterType 387 return activeSelectionList().mtoIter(**kwargs)
388
389 -def select(*nodesOrSelectionList , **kwargs):
390 """Select the given list of wrapped nodes or selection list in maya 391 392 :param nodesOrSelectionList: single selection list or multiple wrapped nodes 393 , or multiple names 394 :param kwargs: 395 * listAdjustment: default api.MGlobal.kReplaceList 396 :note: as this is a convenience function that is not required by the api itself, 397 but for interactive sessions, it will be undoable 398 :note: Components are only supported if a selection list is given 399 :note: This method is implicitly undoable""" 400 nodenames = list() 401 other = list() 402 403 for item in nodesOrSelectionList: 404 if isinstance(item, basestring): 405 nodenames.append(item) 406 else: 407 other.append(item) 408 # END handel item type 409 # END for each item 410 411 if len(other) == 1 and isinstance(other[0], api.MSelectionList): 412 other = other[0] 413 414 sellist = toSelectionList(other) 415 416 if nodenames: 417 sellistnames = toSelectionListFromNames(nodenames) 418 sellist.merge(sellistnames) 419 420 adjustment = kwargs.get("listAdjustment", api.MGlobal.kReplaceList) 421 api.MGlobal.selectCommand(sellist , adjustment)
422
423 424 @undoable 425 -def createNode(nodename, nodetype, autocreateNamespace=True, renameOnClash = True, 426 forceNewLeaf=True , maxShapesPerTransform = 0):
427 """Create a new node of nodetype with given nodename 428 429 :param nodename: like ``mynode``or ``namespace:mynode`` or ``|parent|mynode`` or 430 ``|ns1:parent|ns1:ns2:parent|ns3:mynode``. The name may contain any amount of parents 431 and/or namespaces. 432 :note: For reasons of safety, dag nodes must use absolute paths like ``|parent|child`` - 433 otherwise names might be ambiguous ! This method will assume absolute paths ! 434 :param nodetype: a nodetype known to maya to be created accordingly 435 :param autocreateNamespace: if True, namespaces given in the nodename will be created 436 if required 437 :param renameOnClash: if True, nameclashes will automatcially be resolved by creating a unique 438 name - this only happens if a dependency node has the same name as a dag node 439 :param forceNewLeaf: if True, nodes will be created anyway if a node with the same name 440 already exists - this will recreate the leaf portion of the given paths. Implies renameOnClash 441 If False, you will receive an already existing node if the name and type matches. 442 :param maxShapesPerTransform: only used when renameOnClash is True, defining the number of 443 shapes you may have below a transform. If the number would be exeeded by the creation of 444 a shape below a given transform, a new auto-renamed transform will be created automatically. 445 This transform is garantueed to be new and will be used as new parent for the shape. 446 :raise RuntimeError: If nodename contains namespaces or parents that may not be created 447 :raise NameError: If name of desired node clashes as existing node has different type 448 :note: As this method is checking a lot and tries to be smart, its relatively slow (creates ~1200 nodes / s) 449 :return: the newly create Node""" 450 if nodename in ('|', ''): 451 raise RuntimeError("Cannot create '|' or ''") 452 453 subpaths = nodename.split('|') 454 455 parentnode = None 456 createdNode = None 457 lenSubpaths = len(subpaths) 458 start_index = 1 459 460 # SANITY CHECK ! Must use absolute dag paths 461 if nodename[0] != '|': 462 nodename = "|" + nodename # update with pipe 463 subpaths.insert(0, '') 464 lenSubpaths += 1 465 # END special handling 466 467 added_operation = False 468 is_transform_type = nodetype == 'transform' 469 is_shape = False 470 if not is_transform_type and nodeTypeTree.has_node(nodetype): 471 parents = list(nodeTypeTree.parent_iter(nodetype)) 472 is_transform_type = 'transform' in parents 473 is_shape = 'shape' in parents 474 # END do more intense inheritance query 475 476 do_existence_checks = True 477 dgmod = None 478 dagmod = None 479 for i in xrange(start_index, lenSubpaths): # first token always pipe, need absolute paths 480 nodepartialname = '|'.join(subpaths[0 : i+1]) # full path to the node so far 481 is_last_iteration = i == lenSubpaths - 1 482 483 # DAG ITEM EXISTS ? 484 ###################### 485 # if it doesn't exist the first time, we can save all other checks as we 486 # will start creating it from now on 487 if do_existence_checks: 488 nodeapiobj = toApiobj(nodepartialname) 489 if nodeapiobj is not None: 490 # could be that the node already existed, but with an incorrect type 491 if is_last_iteration: # in the last iteration 492 if not forceNewLeaf: 493 parentnode = createdNode = nodeapiobj 494 _mfndep_setobject(createdNode) 495 existing_node_type = uncapitalize(_mfndep_typename()) 496 nodetypecmp = uncapitalize(nodetype) 497 if nodetypecmp != existing_node_type: 498 # allow more specialized types, but not less specialized ones 499 if nodetypecmp not in nodeTypeTree.parent_iter(existing_node_type): 500 msg = "node %s did already exist, its type %s is incompatible with the requested type %s" % (nodepartialname, existing_node_type, nodetype) 501 raise NameError(msg) 502 # END nodetypes different 503 504 continue 505 # END force new leaf handling 506 else: 507 # just go ahead, but create a new node 508 renameOnClash = True # allow clashes and rename 509 # END leaf path handling 510 else: 511 # remember what we have done so far and continue 512 parentnode = createdNode = nodeapiobj 513 continue 514 else: 515 do_existence_checks = False 516 # END node item exists handling 517 # END do existence checks 518 519 520 521 # it does not exist, check the namespace 522 dagtoken = '|'.join(subpaths[i : i+1]) 523 524 if autocreateNamespace: 525 nsm.createNamespace(":".join(dagtoken.split(":")[0:-1])) # will resolve to root namespace at least 526 527 # see whether we have to create a transform or the actual nodetype 528 actualtype = "transform" 529 if is_last_iteration: 530 actualtype = nodetype 531 532 # create the node - either with or without parent 533 # The actual node needs to be created with a matching modifier, dag nodes 534 # with the DagMofier, dg nodes with the dg modifier 535 # The user currently has to specify a proper path. 536 if parentnode or actualtype == "transform" or (is_last_iteration and is_transform_type): 537 if dagmod is None: 538 dagmod = api.MDagModifier() 539 540 # create dag node 541 newapiobj = None 542 if parentnode: # use parent 543 newapiobj = dagmod.createNode(actualtype, parentnode) # create with parent 544 else: 545 newapiobj = dagmod.createNode(actualtype) # create 546 547 dagmod.renameNode(newapiobj, dagtoken) # rename 548 549 parentnode = createdNode = newapiobj # update parent 550 else: 551 if dgmod is None: 552 dgmod = api.MDGModifier() 553 554 # create dg node - really have to check for clashes afterwards 555 # It may also be that the user passed in a name which didn't 556 # show that we want a dag node - hence we have to check for failure 557 # and recover 558 mod = dgmod 559 try: 560 newapiobj = dgmod.createNode(actualtype) # create 561 except RuntimeError: 562 if dagmod is None: 563 dagmod = api.MDagModifier() 564 mod = dagmod 565 # even though it could be a transform derived type which can be 566 # created right away without an explicit parent, we don't know that, 567 # if we would know, we wouldn't be here. 568 # In case its not transform derived, a parent node would be created 569 # automatically. Problem is that this node is returned instead of 570 # the node we requested (logic bug if you ask me !), and we don't 571 # want to return a node type the caller didn't order. 572 # This is why we explicitly create a transform, to have the parent 573 # under our control. This will put even a transform derived type 574 # under an extra transform, which in that case would not be required. 575 # But here we are, and it cannot be helped. 576 if is_shape: 577 trans = dagmod.createNode("transform") 578 newapiobj = dagmod.createNode(actualtype, trans) 579 else: 580 newapiobj = dagmod.createNode(actualtype) 581 # END shape handling 582 # END handle dag node 583 mod.renameNode(newapiobj, dagtoken) # rename 584 createdNode = newapiobj 585 # END (partial) node creation 586 587 # CLASHING CHECK (and name update) ! 588 # PROBLEM: if a dep node with name of dagtoken already exists, it will 589 # rename the newly created (sub) node although it is not the same ! 590 _mfndep_setobject(newapiobj) 591 actualname = _mfndep_name() 592 if actualname != dagtoken: 593 # Is it a renamed node because because a dep node of the same name existed ? 594 # Could be that a child of the same name existed too 595 if not renameOnClash: 596 msg = "named %s did already exist - cannot create a dag node with same name due to maya limitation" % nodepartialname 597 raise NameError(msg) 598 else: 599 # update the tokens and use the new path 600 subpaths[i] = actualname 601 nodepartialname = '|'.join(subpaths[0 : i+1]) 602 # END dag token renamed 603 # END for each partial path 604 605 # add the modifiers to the undo stack 606 op = undo.GenericOperationStack() 607 if dgmod is not None: 608 op.addCmd(dgmod.doIt, dgmod.undoIt) 609 if dagmod is not None: 610 op.addCmd(dagmod.doIt, dagmod.undoIt) 611 op.doIt() 612 613 if createdNode is None: 614 raise RuntimeError("Failed to create %s (%s)" % (nodename, nodetype)) 615 616 return NodeFromObj(createdNode)
617
618 #} END base 619 620 621 -def _checkedInstanceCreationDagPathSupport(mobject_or_mdagpath, clsToBeCreated, basecls):
622 """Same purpose and attribtues as `_checkedInstanceCreation`, but supports 623 dagPaths as input as well""" 624 apiobj = mobject_or_mdagpath 625 dagpath = None 626 if isinstance(mobject_or_mdagpath, MDagPath): 627 dagpath = mobject_or_mdagpath 628 # END if we have a dag path 629 630 clsinstance = _checkedInstanceCreation(mobject_or_mdagpath, _lookup_type(mobject_or_mdagpath), clsToBeCreated, basecls) 631 if isinstance(clsinstance, DagNode): 632 _setupDagNodeDelayedMethods(clsinstance, apiobj, dagpath) 633 634 return clsinstance
635
636 -def _checkedInstanceCreation(apiobj, typeName, clsToBeCreated, basecls):
637 """Utiliy method creating a new class instance according to additional type information 638 Its used by __new__ constructors to finalize class creation 639 640 :param apiobj: the MObject or MDagPath of object to wrap 641 :param typeName: the name of the node type to be created 642 :param clsToBeCreated: the cls object as passed in to __new__ 643 :param basecls: the class of the caller containing the __new__ method 644 :return: create clsinstance if the proper type (according to nodeTypeTree)""" 645 # get the node type class for the api type object 646 647 nodeTypeCls = nodeTypeToNodeTypeCls(typeName, apiobj) 648 649 # NON-MAYA NODE Type 650 # if an explicit type was requested, assure we are at least compatible with 651 # the given cls type - our node type is supposed to be the most specialized one 652 # cls is either of the same type as ours, or is a superclass. 653 # It is also okay if the user provided a class which is a subclass of the most 654 # suitable class we know, which acts like a virtal specialization 655 if clsToBeCreated is not basecls and clsToBeCreated is not nodeTypeCls: 656 vclass_attr = '__mrv_virtual_subtype__' 657 # If the class is a virtual subtype and indeed a subclass of our best known type, 658 # its a valid class 659 if not issubclass(nodeTypeCls, clsToBeCreated) and \ 660 not (hasattr(clsToBeCreated, vclass_attr) and issubclass(clsToBeCreated, nodeTypeCls)): 661 raise TypeError("Explicit class %r must be %r or a superclass of it. Consider setting the %s attribute to indicate you are a virtual subtype." % (clsToBeCreated, nodeTypeCls, vclass_attr)) 662 else: 663 nodeTypeCls = clsToBeCreated # respect the wish of the client 664 # END if explicit class given 665 666 # FINISH INSTANCE 667 # At this point, we only support type as we expect ourselves to be lowlevel 668 clsinstance = object.__new__(nodeTypeCls) 669 670 object.__setattr__(clsinstance, '_apiobj', apiobj) 671 return clsinstance
672
673 -def _setupDagNodeDelayedMethods(dagnode, mobject, mdagpath):
674 """Setup the given dagnode with the instance methods it needs to handle the gven 675 mobject OR mdagpath accordingly, one of them may be None""" 676 instcls = type(dagnode) 677 if mdagpath is None: 678 # next time the MDagPath is accessed, we retrieve it from the MObject 679 object.__setattr__(dagnode, 'dagPath', instancemethod(instcls._dagPath_delayed, dagnode, instcls)) 680 object.__setattr__(dagnode, 'object', instancemethod(instcls._object_cached, dagnode, instcls)) 681 else: 682 # MObject has to be retrieved on demand 683 # this is the default 684 object.__setattr__(dagnode, '_apidagpath', mdagpath)
685 # END handle missing MDagPath or MObject
686 687 688 -def _createInstByPredicate(apiobj, cls, basecls, predicate):
689 """Allows to wrap objects around MObjects where the actual compatabilty 690 cannot be determined by some nodetypename, but by the function set itself. 691 Thus it uses the nodeTypeToMfnClsMap to get mfn cls for testing 692 693 :param cls: the class to be created 694 :param basecls: the class where __new__ has actually been called 695 :param predicate: returns true if the given nodetypename is valid, and its mfn 696 should be taken for tests 697 :return: new class instance, or None if no mfn matched the apiobject""" 698 # try which node type fits 699 # All attribute instances end with attribute 700 # NOTE: the capital case 'A' assure we do not get this base class as option - this would 701 # be bad as it is compatible with all classes 702 attrtypekeys = [a for a in nodeTypeToMfnClsMap.keys() if predicate(a)] 703 704 for attrtype in attrtypekeys: 705 attrmfncls = nodeTypeToMfnClsMap[attrtype] 706 try: 707 mfn = attrmfncls(apiobj) 708 except RuntimeError: 709 continue 710 else: 711 newinst = _checkedInstanceCreation(apiobj, attrtype, cls, basecls) # lookup in node tree 712 return newinst 713 # END for each known attr type 714 return None
715
716 717 -def _getUniqueName(dagpath):
718 """Create a unique name based on the given dagpath by appending numbers""" 719 copynumber = 1 720 newpath = str(dagpath) 721 while cmds.objExists(newpath): 722 newpath = "%s%i" % (dagpath, copynumber) 723 copynumber += 1 724 # END while dagpath does exist 725 return newpath
726
727 ############################ 728 #### Classes #### 729 ########################## 730 731 732 #{ Utilities 733 -class SetFilter(tuple):
734 """Utility Class returning True or False on call, latter one if 735 the passed object does not match the filter"""
736 - def __new__(cls, apitype, exactTypeFlag, deformerSet):
737 return tuple.__new__(cls, (apitype, exactTypeFlag, deformerSet))
738
739 - def __call__(self, apiobj):
740 """:return: True if given api object matches our specifications """ 741 if self[2]: # deformer sets 742 setnode = NodeFromObj(apiobj) 743 for elmplug in setnode.usedBy: # find connected deformer 744 iplug = elmplug.minput() 745 if iplug.isNull(): 746 continue 747 748 if iplug.node().hasFn(api.MFn.kGeometryFilt): 749 return True 750 # END for each connected plug in usedBy array 751 752 return False # no deformer found 753 # deformer set handling 754 755 if self[1]: # exact type 756 return apiobj.apiType() == self[0] 757 758 # not exact type 759 return apiobj.hasFn(self[0])
760 # END SetFilter 761 762 #} END utilities 763 764 765 #{ Base 766 767 _api_type_tuple = (MObject, MDagPath)
768 769 -class Node(object):
770 """Common base for all maya nodes, providing access to the maya internal object 771 representation 772 Use this class to directly create a maya node of the required type""" 773 __metaclass__ = MetaClassCreatorNodes 774
775 - def __new__ (cls, *args, **kwargs):
776 """return the proper class for the given object 777 778 :param args: arg[0] is the node to be wrapped 779 780 * string: wrap the API object with the respective name 781 * MObject 782 * MObjectHandle 783 * MDagPath 784 785 If args is empty, a new node of the given type will be created within 786 maya. Shapes will automatically receive a parent transform. 787 kwargs will be passed to `createNode` in that case. 788 :note: This multi-purpose constructor is not perfectly optimized for speed, 789 consider using `NodeFromObj` instead""" 790 791 if not args: 792 if not issubclass(cls, DependNode): # cls can be DependNode as well 793 raise TypeError("Can only create types being subclasses of Node, not %r" % cls) 794 # END handle invalid class 795 796 typename = uncapitalize(cls.__name__) 797 instname = typename 798 if issubclass(cls, Shape): # cls can be DagNode as well 799 instname = "%s|%sShape" % (instname, instname) 800 # END handle dag objects 801 802 return createNode(instname, typename, **kwargs) 803 # END handle creation mode 804 805 objorname = args[0] 806 mobject_or_mdagpath = None 807 808 # GET AN API OBJECT 809 if isinstance(objorname, _api_type_tuple): 810 mobject_or_mdagpath = objorname 811 elif isinstance(objorname, basestring): 812 if objorname.find('.') != -1: 813 raise ValueError("%s cannot be handled - create a node, then access its attribute like Node('name').attr" % objorname) 814 mobject_or_mdagpath = toApiobjOrDagPath(objorname) 815 elif isinstance(objorname, MObjectHandle): 816 mobject_or_mdagpath = objorname.object() 817 else: 818 raise TypeError("Objects of type %s cannot be handled" % type(objorname)) 819 820 821 skip_checks = (len(args) > 1 and args[1]) or False 822 if (not skip_checks and (mobject_or_mdagpath is None 823 or (isinstance(mobject_or_mdagpath, MDagPath) and not mobject_or_mdagpath.isValid()) 824 or (isinstance(mobject_or_mdagpath, MObject) and mobject_or_mdagpath.isNull()))): 825 raise ValueError("object does not exist: %s" % objorname) 826 # END evil validity checking 827 828 # CREATE INSTANCE 829 return _checkedInstanceCreationDagPathSupport(mobject_or_mdagpath, cls, Node)
830 831 832 #{ Overridden Methods
833 - def __eq__(self, other):
834 """compare the nodes according to their api object. 835 Valid inputs are other Node, MObject or MDagPath instances""" 836 otherapiobj = None 837 if not isinstance(other, Node): 838 otherapiobj = NodeFromObj(other).object() 839 else: # assume Node 840 otherapiobj = other.object() 841 # END handle types 842 843 return self.object() == otherapiobj # does not appear to work as expected ...
844
845 - def __ne__(self, other):
846 return not Node.__eq__(self, other)
847
848 - def __hash__(self):
849 """ 850 :return: our name as hash - as python keeps a pool, each name will 851 correspond to the exact object. 852 :note: using asHashable of openMayaMPx did not work as it returns addresses 853 to instances - this does not work for MObjects though 854 :note: in maya2009 and newer, MObjectHandle.hashCode provides the information 855 we need, faster""" 856 return hash(str(self))
857 858 if hasattr(api.MObjectHandle, 'hashCode'):
859 - def __hash_2009__(self):
860 """:return: hash of our object using MObjectHandle functionlity""" 861 return MObjectHandle(self.object()).hashCode()
862 863 __hash__ = __hash_2009__ 864 __hash__.__name__ = '__hash__' 865 # END overwrite previous hash with faster version 866 867 #} END overridden methods 868 869 #{ Interface
870 - def apiObject(self):
871 """ 872 :return: the highest qualified api object of the actual superclass, 873 usually either MObject or MDagPath""" 874 raise NotImplementedError("To be implemented in subclass")
875 876 @classmethod
877 - def getMFnClasses(cls):
878 """ 879 :return: list of all function set classes this node supports, most derived 880 function set comes first""" 881 return [mrocls._mfncls for mrocls in cls.mro() if '_mfncls' in mrocls.__dict__]
882
883 - def apiType(self):
884 """:return: the MFn Type id of the wrapped object""" 885 return self.apiObject().apiType()
886
887 - def hasFn(self, mfntype):
888 """:return: True if our object supports the given function set type""" 889 return self.apiObject().hasFn(mfntype)
890
891 #} END interface 892 893 -def _lookup_type(mobject_or_mdagpath):
894 """:return: node type name of the given MObject or MDagPath 895 :note: if we have a plugin type, we must use the 'slow' way 896 as the type is the same for all plugin nodes""" 897 apitype = mobject_or_mdagpath.apiType() 898 try: 899 if apitype in _plugin_type_ids_lut: 900 raise KeyError 901 # END force byName type check for plugin types 902 return _apitype_to_name[apitype] 903 except KeyError: 904 # cache miss - fill in the type 905 if isinstance(mobject_or_mdagpath, MDagPath): 906 _mfndag_setObject(mobject_or_mdagpath) 907 typename =_mfndag_typename() 908 else: 909 _mfndep_setobject(mobject_or_mdagpath) 910 typename = _mfndep_typename() 911 # END handle input type 912 _apitype_to_name[mobject_or_mdagpath.apiType()] = typename 913 914 return typename
915 # END handle cache miss
916 917 918 -class NodeFromObj(object):
919 """Virtual Constructor, producing nodes as the `Node` does, but it will only 920 accept MObjects or dagpaths which are expected to be valid. 921 As no additional checking is performed, it might be more unsafe to use, but 922 will be faster as it does not perform any runtime checks 923 924 It duplicates code from `_checkedInstanceCreation` and `_checkedInstanceCreationDagPathSupport` 925 to squeeze out the last tiny bit of performance as it can make quite a few more 926 assumptions and reduces method calls. 927 928 :note: Do not derive from this class, derive from `Node` instead 929 :note: We will always create the node type as determined by the type hierarchy"""
930 - def __new__ (cls, mobject_or_mdagpath):
931 apiobj = mobject_or_mdagpath 932 dagpath = None 933 if isinstance(mobject_or_mdagpath, MDagPath): 934 dagpath = mobject_or_mdagpath 935 # END if we have a dag path 936 937 clsinstance = object.__new__(nodeTypeToNodeTypeCls(_lookup_type(mobject_or_mdagpath), apiobj)) 938 939 # apiobj is None, or MObject, or MDagPath, but will be set to the proper type 940 # later 941 object.__setattr__(clsinstance, '_apiobj', apiobj) 942 943 # DagNode created from a MObject ? 944 if isinstance(clsinstance, DagNode): 945 _setupDagNodeDelayedMethods(clsinstance, apiobj, dagpath) 946 # END handel DagObjects 947 948 # for some reason, we have to call init ourselves in that case, probably 949 # since we are not afficliated with the actual instance we returned which 950 # makes a little bit of sense. 951 clsinstance.__init__(mobject_or_mdagpath) 952 return clsinstance
953
954 955 -class NodeFromStr(object):
956 """Virtual constructor similar to `NodeFromObj`, but it will only accept strings 957 to produce a wrapped node as fast as possible. Therefore, the error checking is 958 left out."""
959 - def __new__ (cls, node_string):
960 return NodeFromObj(toApiobjOrDagPath(node_string))
961
962 963 -class DependNode(Node, iDuplicatable): # parent just for epydoc -
964 """ Implements access to dependency nodes""" 965 966 #{ Overridden Methods
967 - def __getattr__(self, attr):
968 """Interpret attributes not in our dict as attributes on the wrapped node, 969 create a plug for it and add it to our class dict, effectively caching the attribute""" 970 base = super(DependNode, self) 971 try: 972 plug = self.findPlug(attr) 973 except RuntimeError: # perhaps a base class can handle it 974 try: 975 return base.__getattribute__(attr) 976 except AttributeError: 977 raise AttributeError("Attribute '%s' does not exist on '%s', neither as function not as attribute" % (attr, self.name())) 978 # END try to get attribute by base class 979 # END find plug exception handling 980 981 # NOTE: Don't cache the plug on the instance, it might be too dangerous 982 # in conjunction with changes to the DAG 983 984 # and assure our class knows about it so in future the plug will be retrieved 985 # right away, before having a function lookup miss 986 attr = str(attr) 987 setattr(type(self), attr, property(lambda self: self.findPlug(attr))) 988 989 return plug
990
991 - def __str__(self):
992 """:return: name of this object""" 993 return self.name()
994
995 - def __repr__(self):
996 """:return: class call syntax""" 997 import traceback 998 return '%s("%s")' % (self.__class__.__name__, DependNode.__str__(self))
999 #} END overridden methods 1000 1001 1002 #(iDuplicatable 1003 1004 @notundoable
1005 - def duplicate(self, name = None, *args, **kwargs):
1006 """Duplicate our node and return a wrapped version to it 1007 1008 :param name: if given, the newly created node will use the given name 1009 :param kwargs: 1010 * renameOnClash: if Trrue, default True, clashes are prevented by renaming the new node 1011 * autocreateNamespace: if True, default True, namespaces will be created if mentioned in the name 1012 :note: the copyTo method may not have not-undoable side-effects to be a proper 1013 implementation 1014 :note: undo could be implemented for dg nodes - but for reasons of consistency, its disabled here - 1015 who knows how much it will crap out after a while as duplicate is not undoable (mel command) - 1016 it never really worked to undo a mel command from within python, executed using a dgmodifier - unfortunately 1017 it does not return any result making it hard to find the newly duplicated object !""" 1018 # returns name of duplicated node 1019 duplnode = NodeFromStr(cmds.duplicate(str(self))[0]) 1020 1021 # RENAME 1022 ########### 1023 # find a good name based on our own one - the default name is just not nice 1024 if not name: 1025 name = _getUniqueName(self) 1026 else: 1027 if '|' in name: 1028 raise ValueError("Names for dependency nodes my not contain pipes: %s" % name) 1029 # END name handling 1030 1031 rkwargs = dict() 1032 rkwargs['renameOnClash'] = kwargs.pop('renameOnClash', True) 1033 rkwargs['autocreateNamespace'] = kwargs.pop('autocreateNamespace', True) 1034 duplnode = duplnode.rename(name, **rkwargs) 1035 1036 # call our base class to copy additional information 1037 self.copyTo(duplnode, *args, **kwargs) 1038 return duplnode
1039 1040 #) END iDuplicatable 1041 1042 #{ preset type filters 1043 fSetsObject = SetFilter(api.MFn.kSet, True, 0) # object fSets only 1044 fSets = SetFilter(api.MFn.kSet, False, 0) # all set types 1045 #} END type filters 1046 1047 #{ Sets Handling 1048
1049 - def _getSetPlug(self):
1050 """:return: message plug - for non dag nodes, this will be connected """ 1051 return self.message
1052
1053 - def connectedSets(self, setFilter = fSets):
1054 """:return: list of object set compatible Nodes having self as member 1055 :param setFilter: tuple(apiType, use_exact_type) - the combination of the 1056 desired api type and the exact type flag allow precise control whether you which 1057 to get only renderable shading engines, only objectfSets (tuple[1] = True), 1058 or all objects supporting the given object type. 1059 Its preset to only return shading engines 1060 :note: the returned sets order is defined by the order connections to instObjGroups 1061 :note: only sets will be returned that have the whole object as member, thus you will not 1062 see sets having component assignments like per-compoent shader assignments or deformer sets """ 1063 1064 # have to parse the connections to fSets manually, finding fSets matching the required 1065 # type and returning them 1066 outlist = list() 1067 iogplug = self._getSetPlug() 1068 1069 for dplug in iogplug.moutputs(): 1070 setapiobj = dplug.node() 1071 1072 if not setFilter(setapiobj): 1073 continue 1074 outlist.append(NodeFromObj(MObject(setapiobj))) 1075 # END for each connected set 1076 1077 return outlist
1078 1079 # alias - connectedSets derives from the MayaAPI, but could be shorter 1080 sets = connectedSets 1081
1082 - def isMemberOf(self, setnode, component = MObject()):
1083 """:return: True if self is part of setnode 1084 :note: method is undoable 1085 :see: `sets.ObjectSet`""" 1086 return setnode.isMember(self, component = component)
1087
1088 - def addTo(self, setnode, component = MObject(), **kwargs):
1089 """Add ourselves to the given set 1090 1091 :note: method is undoable 1092 :see: `sets.ObjectSet`""" 1093 return setnode.addMember(self, component = component, **kwargs)
1094
1095 - def removeFrom(self, setnode, component = MObject()):
1096 """remove ourselves to the given set 1097 1098 :note: method is undoable 1099 :see: `sets.ObjectSet`""" 1100 return setnode.removeMember(self, component = component)
1101 1102 #} END sets handling 1103 1104 #{ Edit 1105 1106 @undoable
1107 - def rename(self, newname, autocreateNamespace=True, renameOnClash = True):
1108 """Rename this node to newname 1109 1110 :param newname: new name of the node 1111 :param autocreateNamespace: if true, namespaces given in newpath will be created automatically, otherwise 1112 a RuntimeException will be thrown if a required namespace does not exist 1113 :param renameOnClash: if true, clashing names will automatically be resolved by adjusting the name 1114 :return: renamed node which is the node itself 1115 :note: for safety reasons, this node is dagnode aware and uses a dag modifier for them !""" 1116 if '|' in newname: 1117 raise NameError("new node names may not contain '|' as in %s" % newname) 1118 1119 # is it the same name ? 1120 if newname == api.MFnDependencyNode(self.object()).name(): 1121 return self 1122 1123 # ALREADY EXISTS ? 1124 if not renameOnClash: 1125 exists = False 1126 1127 if isinstance(self, DagNode): # dagnode: check existing children under parent 1128 parent = self.parent() 1129 if parent: 1130 testforobject = parent.fullChildName(newname) # append our name to the path 1131 if objExists(testforobject): 1132 raise RuntimeError("Object %s did already exist - renameOnClash could have resolved this issue" % testforobject) 1133 # END if we have a parent 1134 else: 1135 exists = objExists(newname) # depnode: check if object exists 1136 1137 if exists: 1138 raise RuntimeError("Node named %s did already exist, failed to rename %s" % (newname, self)) 1139 # END not renameOnClash handling 1140 1141 # NAMESPACE 1142 ns = ":".join(newname.split(":")[:-1]) 1143 if not nsm.existsNamespace(ns) and not autocreateNamespace: 1144 raise RuntimeError("Cannot rename %s to %s as namespace %s does not exist" % (self, newname, ns)) 1145 ns = nsm.createNamespace(ns) # assure its there 1146 1147 1148 # NOTE: this stupid method will also rename shapes !!! 1149 # you cannot prevent it, so we have to store the names and rename it lateron !! 1150 shapenames = shapes = None # HACK: this is dagnodes only (only put here for convenience, should be in DagNode) 1151 1152 # rename the node 1153 mod = None 1154 if isinstance(self, DagNode): 1155 mod = undo.DagModifier() 1156 shapes = self.shapes() 1157 shapenames = [s.basename() for s in shapes ] 1158 else: 1159 mod = undo.DGModifier() 1160 mod.renameNode(self.object(), newname) 1161 1162 1163 # RENAME SHAPES BACK ! 1164 ####################### 1165 # Yeah, of course the rename method renames shapes although this has never been 1166 # requested ... its so stupid ... 1167 if shapes: 1168 for shape,shapeorigname in zip(shapes, shapenames): # could use izip, but this is not about memory here 1169 mod.renameNode(shape.object(), shapeorigname) 1170 # END for each shape to rename 1171 # END handle renamed shapes 1172 1173 mod.doIt() 1174 1175 return self
1176 1177 @undoable
1178 - def delete(self):
1179 """Delete this node 1180 1181 :note: if the undo queue is enabled, the object becomes invalid, but stays alive until it 1182 drops off the queue 1183 :note: if you want to delete many nodes, its more efficient to delete them 1184 using the global `delete` method""" 1185 mod = undo.DGModifier() 1186 mod.deleteNode(self.object()) 1187 mod.doIt()
1188
1189 - def _addRemoveAttr(self, attr, add):
1190 """DoIt function adding or removing attributes with undo""" 1191 mfninst = self._mfncls(self.object()) 1192 doitfunc = mfninst.addAttribute 1193 1194 if not add: 1195 doitfunc = mfninst.removeAttribute 1196 1197 doitfunc(attr)
1198
1199 - def addAttribute(self, attr):
1200 """Add the given attribute to the node as local dynamic attribute 1201 1202 :param attr: MObject of attribute or Attribute instance as retrieved from 1203 a plug 1204 :return: plug to the newly added attribute 1205 :note: This method is explicitly not undoable as attributes are being deleted 1206 in memory right in the moment they are being removed, thus they cannot 1207 reside on the undo queue""" 1208 # return it if it already exists 1209 attrname = api.MFnAttribute(attr).name() 1210 try: 1211 return self.findPlug(attrname, False) 1212 except RuntimeError: 1213 pass 1214 1215 self._addRemoveAttr(attr, True) 1216 return self.findPlug(api.MFnAttribute(attr).name())
1217
1218 - def removeAttribute(self, attr):
1219 """Remove the given attribute from the node 1220 1221 :param attr: see `addAttribute`""" 1222 # don't do anyting if it does not exist 1223 attrname = api.MFnAttribute(attr).name() 1224 try: 1225 self.findPlug(attrname, False) 1226 except RuntimeError: 1227 # it does not exist, that's what was requested 1228 return 1229 1230 self._addRemoveAttr(attr, False)
1231 1232 @undoable
1233 - def setNamespace(self, newns, **kwargs):
1234 """ 1235 :return: self after being moved to the given namespace. This will effectively 1236 rename the object. 1237 :param newns: Namespace instance to put this Node into 1238 :param kwargs: to be passed to `rename`""" 1239 namespace, objname = nsm.Namespace.splitNamespace(self.basename()) 1240 return self.rename(newns + objname, **kwargs)
1241 1242 #} END edit 1243 1244 @undoable
1245 - def setLocked(self, state):
1246 """Lock or unloack this node 1247 1248 :param state: if True, the node is locked. Locked nodes cannot be deleted, 1249 renamed or reparented 1250 :note: you can query the lock state with `isLocked`""" 1251 curstate = self.isLocked() 1252 # also works for dag nodes ! 1253 depfn = api.MFnDependencyNode(self.object()) 1254 1255 op = undo.GenericOperation() 1256 op.setDoitCmd(depfn.setLocked, state) 1257 op.setUndoitCmd(depfn.setLocked, curstate) 1258 op.doIt()
1259 1260 1261 #{ Connections and Attributes 1262
1263 - def connections(self):
1264 """:return: MPlugArray of connected plugs""" 1265 cons = api.MPlugArray() 1266 mfn = DependNode._mfncls(self.object()).getConnections(cons) 1267 return cons
1268
1269 - def dependencyInfo(self, attribute, by=True):
1270 """ 1271 :return: list of attributes that given attribute affects or that the given attribute 1272 is affected by 1273 if the attribute turns dirty. 1274 :param attribute: attribute instance or attribute name 1275 :param by: if false, affected attributes will be returned, otherwise the attributes affecting this one 1276 :note: see also `MPlug.affectedByPlugs` 1277 :note: USING MEL: as api command and mObject array always crashed on me ... don't know :(""" 1278 if not isinstance(attribute, basestring): 1279 attribute = attribute.name() 1280 # END handle input 1281 attrs = cmds.affects(attribute , str(self), by=by) 1282 return [self.attribute(an) for an in attrs]
1283 1284 #} END connections and attribtues 1285 1286 #{ Status
1287 - def isValid(self):
1288 """:return: True if the object exists in the scene 1289 :note: objects on the undo queue are NOT valid, but alive""" 1290 return MObjectHandle(self.object()).isValid()
1291
1292 - def isAlive(self):
1293 """:return: True if the object exists in memory 1294 :note: objects on the undo queue are alive, but NOT valid""" 1295 return MObjectHandle(self.object()).isAlive()
1296 1297 #} END status 1298 1299 #{ General Query
1300 - def object(self):
1301 """:return: the MObject attached to this Node""" 1302 return self._apiobj
1303 1304 apiObject = object # overridden from Node 1305
1306 - def referenceFile(self):
1307 """ 1308 :return: name (str) of file this node is coming from - it could contain 1309 a copy number as {x} 1310 :note: will raise if the node is not referenced, use isReferenced to figure 1311 that out""" 1312 # apparently, we have to use MEL here :( 1313 return cmds.referenceQuery(str(self) , f=1)
1314
1315 - def basename(self):
1316 """:return: name of this instance 1317 :note: it is mainly for compatability with dagNodes which need this method 1318 in order to return the name of their leaf node""" 1319 return self.name()
1320 1321 #}END general query 1322 1323 1324 if env.appVersion() < 2012:
1325 - class Entity(DependNode):
1326 """Common base for dagnodes and paritions 1327 1328 :note: parent is set by metacls. Parent differs between maya2011 and newer versions, 1329 hence we cannot provide it here for use by the documnetation system"""
1330 else:
1331 - class ContainerBase(DependNode):
1332 pass
1333
1334 - class Entity(ContainerBase):
1335 pass
1336 #END handle maya version differences
1337 1338 1339 -class DagNode(Entity, iDagItem): # parent just for epydoc
1340 """ Implements access to DAG nodes""" 1341 1342 _sep = "|" 1343 kNextPos = MFnDagNode.kNextPos 1344
1345 - def __eq__(self, other):
1346 """Compare MDagPaths directly 1347 Valid inputs are Node, DagNode, MObject and MDagPath instances.""" 1348 if not isinstance(other, Node): 1349 other = NodeFromObj(other) 1350 if isinstance(other, DagNode): 1351 return self.dagPath() == other.dagPath() 1352 return self.object() == other.object()
1353
1354 - def __ne__(self, other):
1355 return not DagNode.__eq__(self, other)
1356
1357 - def __getitem__(self, index):
1358 """ 1359 :return: if index >= 0: Node(child) at index 1360 1361 * if index < 0: Node parent at -(index+1)(if walking up the hierarchy) 1362 * If index is string, use DependNodes implementation 1363 1364 :note: returned child can be transform or shape, use `shapes` or 1365 `childTransforms` if you need a quickfilter """ 1366 if index > -1: 1367 return self.child(index) 1368 else: 1369 for i,parent in enumerate(self.iterParents()): 1370 if i == -(index+1): 1371 return parent 1372 # END for each parent 1373 raise IndexError("Parent with index %i did not exist for %r" % (index, self))
1374 1375
1376 - def _getSetPlug(self):
1377 """ 1378 :return: the iogplug properly initialized for self 1379 Dag Nodes have the iog plug as they support instancing """ 1380 return self.iog.elementByLogicalIndex(self.instanceNumber())
1381 1382 #{ DAG Modification 1383
1384 - def _setWorldspaceTransform(self, parentnode):
1385 """Set ourselve's transformation matrix to our absolute worldspace transformation, 1386 possibly relative to the optional parentnode 1387 1388 :param parentnode: if not None, it is assumed to be the future parent of the node, 1389 our transformation will be set such that we retain our worldspace position if parented below 1390 parentnode""" 1391 if not isinstance(self, Transform): 1392 return 1393 1394 nwm = self.wm.elementByLogicalIndex(self.instanceNumber()).masData().transformation().asMatrix() 1395 1396 # compenstate for new parents transformation ? 1397 if parentnode is not None: 1398 # use world - inverse matrix 1399 parentInverseMatrix = parentnode.wim.elementByLogicalIndex(parentnode.instanceNumber()).masData().transformation().asMatrix() 1400 nwm = nwm * parentInverseMatrix 1401 # END if there is a new parent 1402 1403 self.set(api.MTransformationMatrix(nwm))
1404 1405 1406 1407 @undoable
1408 - def reparent(self, parentnode, renameOnClash=True, raiseOnInstance=True, keepWorldSpace = False):
1409 """ Change the parent of all nodes (also instances) to be located below parentnode 1410 1411 :param parentnode: Node instance of transform under which this node should be parented to 1412 if None, node will be reparented under the root (which only works for transforms) 1413 :param renameOnClash: resolve nameclashes by automatically renaming the node to make it unique 1414 :param raiseOnInstance: if True, this method will raise if you try to reparent an instanced object. 1415 If false, instanced objects will be merged into the newly created path under parentnode, effectively 1416 eliminating all other paths , keeping the newly created one 1417 :param keepWorldSpace: if True and node to be reparented is a transform, the world space position 1418 will be kept by adjusting the transformation accordingly. 1419 **WARNNG**: Currently we reset pivots when doing so 1420 1421 :return : copy of self pointing to the new dag path self 1422 1423 :note: will remove all instance of this object and leave this object at only one path - 1424 if this is not what you want, use the addChild method instead as it can properly handle this case 1425 1426 :note: this method handles namespaces properly """ 1427 if raiseOnInstance and self.instanceCount(False) > 1: 1428 raise RuntimeError("%r is instanced - reparent operation would destroy direct instances" % self) 1429 1430 if not renameOnClash and parentnode and self != parentnode: 1431 # check existing children of parent and raise if same name exists 1432 # I think this check must be string based though as we are talking about 1433 # a possbly different api object with the same name - probably api will be faster 1434 testforobject = parentnode.fullChildName(self.basename()) # append our name to the path 1435 if objExists(testforobject): 1436 raise RuntimeError("Object %s did already exist" % testforobject) 1437 # END rename on clash handling 1438 1439 # keep existing transformation ? Set the transformation accordingly beforehand 1440 if keepWorldSpace: 1441 # transform check done in method 1442 self._setWorldspaceTransform(parentnode) 1443 # END if keep worldspace 1444 1445 1446 # As stupid dagmodifier cannot handle instances right (as it works on MObjects 1447 mod = None # create it once we are sure the operation takes place 1448 if parentnode: 1449 if parentnode == self: 1450 raise RuntimeError("Cannot parent object %s under itself" % self) 1451 1452 mod = undo.DagModifier() 1453 mod.reparentNode(self.object(), parentnode.object()) 1454 else: 1455 # sanity check 1456 if isinstance(self, Shape): 1457 raise RuntimeError("Shape %s cannot be parented under root '|' but needs a transform" % self) 1458 mod = undo.DagModifier() 1459 mod.reparentNode(self.object()) 1460 # END handle parent node 1461 1462 mod.doIt() 1463 1464 # UPDATE DAG PATH 1465 # find it in parentnodes children 1466 if parentnode: 1467 for child in parentnode.children(): 1468 if DependNode.__eq__(self, child): 1469 return child 1470 else: # return updated version of ourselves 1471 return NodeFromObj(self.object()) 1472 # END post-handle parent Node 1473 1474 1475 raise AssertionError("Could not find self in children after reparenting")
1476 1477 @undoable
1478 - def unparent(self, **kwargs):
1479 """As `reparent`, but will unparent this transform under the scene root""" 1480 return self.reparent(None, **kwargs)
1481 1482 @undoable
1483 - def addInstancedChild(self, childNode, position=MFnDagNode.kNextPos):
1484 """Add childnode as instanced child to this node 1485 1486 :note: for more information, see `addChild` 1487 :note: its a shortcut to addChild allowing to clearly indicate what is happening""" 1488 return self.addChild(childNode, position = position, keepExistingParent=True)
1489 1490 @undoable
1491 - def removeChild(self, childNode, allowZeroParents = False):
1492 """remove the given childNode (being a child of this node) from our child list, effectively 1493 parenting it under world ! 1494 1495 :param childNode: Node to unparent - if it is not one of our children, no change takes place 1496 :param allowZeroParents: if True, it is possible to leave a node unparented, thus no valid 1497 dag paths leads to it. If False, transforms will just be reparented under the world 1498 :return: copy of childnode pointing to the first valid dag path we find. 1499 :note: to prevent the child (if transform) to dangle in unknown space if the last instance 1500 is to be removed, it will instead be reparented to world. 1501 :note: removing shapes from their last parent will result in an error""" 1502 # reparent if we have a last-instance of something 1503 if not allowZeroParents: 1504 if childNode.instanceCount(False) == 1: 1505 if isinstance(childNode, Transform): 1506 return childNode.reparent(None) 1507 else: 1508 # must be shape - raise 1509 # TODO: could create new transform node which is pretty close to the maya default behaviour 1510 raise RuntimeError("Shapenodes cannot be unparented if no parent transform would be left") 1511 # END if instance count == 1 1512 # END if not allowZeroParents 1513 1514 op = undo.GenericOperation() 1515 dagfn = api.MFnDagNode(self.dagPath()) 1516 1517 # The method will not fail if the child cannot be found in child list 1518 # just go ahead 1519 1520 op.setDoitCmd(dagfn.removeChild, childNode.object()) 1521 op.setUndoitCmd(self.addChild, childNode, keepExistingParent=True) # TODO: add child to position it had 1522 op.doIt() 1523 1524 return NodeFromObj(childNode.object()) # will attach A new dag path respectively - it will just pick the first one it gets
1525 1526 1527 @undoable
1528 - def addChild(self, childNode, position=MFnDagNode.kNextPos, keepExistingParent=False, 1529 renameOnClash=True, keepWorldSpace = False):
1530 """Add the given childNode as child to this Node. Allows instancing ! 1531 1532 :param childNode: Node you wish to add 1533 :param position: the index to which to add the new child, kNextPos will add it as last child. 1534 It supports python style negative indices 1535 :param keepExistingParent: if True, the childNode will be instanced as it will 1536 have its previous parent(s) and this one, if False, the previous parent will be removed 1537 from the child's parent list 1538 :param renameOnClash: resolve nameclashes by automatically renaming the node to make it unique 1539 :param keepWorldSpace: see `reparent`, only effective if the node is not instanced 1540 :return: childNode whose path is pointing to the new child location 1541 :raise ValueError: if keepWorldSpace is requested with directly instanced nodes 1542 :note: as maya internally handles add/remove child as instancing operation, even though 1543 keepExistingParent is False, it will mess up things and for a short period of time in fact 1544 have two n + 1 instances, right before one is unlinked, This still fills a slot or something, and 1545 isInstanced will be true, although the pathcount is 1. 1546 Long story short: if the item to be added to us is not instanced, we use reparent instead. It 1547 will not harm in direct instances, so its save to use. 1548 :note: if the instance count of the item is 1 and keepExistingParent is False, the position 1549 argument is being ignored""" 1550 # should we use reparent to get around an instance bug ? 1551 is_direct_instance = childNode.instanceCount(0) > 1 1552 if not keepExistingParent and not is_direct_instance: # direct only 1553 return childNode.reparent(self, renameOnClash=renameOnClash, raiseOnInstance=False, 1554 keepWorldSpace = keepWorldSpace) 1555 # END reparent if not-instanced 1556 1557 # CHILD ALREADY THERE ? 1558 ######################### 1559 # We do not raise if the user already has what he wants 1560 # check if child is already part of our children 1561 children = None 1562 # lets speed things up - getting children is expensive 1563 if isinstance(childNode, Transform): 1564 children = self.childTransforms() 1565 else: 1566 children = self.shapes() 1567 1568 # compare MObjects 1569 for exChild in children: 1570 if DependNode.__eq__(childNode, exChild): 1571 return exChild # exchild has proper dagpath 1572 del(children) # release memory 1573 1574 if not renameOnClash: 1575 # check existing children of parent and raise if same name exists 1576 # I think this check must be string based though as we are talking about 1577 # a possbly different api object with the same name - probably api will be faster 1578 testforobject = self.fullChildName(childNode.basename()) # append our name to the path 1579 if objExists(testforobject): 1580 raise RuntimeError("Object %s did already exist below %r" % (testforobject , self)) 1581 # END rename on clash handling 1582 1583 1584 # ADD CHILD 1585 ############### 1586 op = undo.GenericOperationStack() 1587 1588 pos = position 1589 if pos != self.kNextPos: 1590 pos = pythonIndex(pos, self.childCount()) 1591 1592 dagfn = api.MFnDagNode(self.dagPath()) 1593 docmd = Call(dagfn.addChild, childNode.object(), pos, True) 1594 undocmd = Call(self.removeChild, childNode) 1595 1596 op.addCmd(docmd, undocmd) 1597 1598 1599 # EXISTING PARENT HANDLING 1600 ############################ 1601 # if we do not keep parents, we also have to re-add it to the original parent 1602 # therefore wer create a dummy do with a real undo 1603 undocmdCall = None 1604 parentTransform = None 1605 if not keepExistingParent: 1606 # remove from childNode from its current parent (could be world !) 1607 parentTransform = childNode.parent() 1608 validParent = parentTransform 1609 if not validParent: 1610 # get the world, but initialize the function set with an mobject ! 1611 # works only in the world case ! 1612 worldobj = api.MFnDagNode(childNode.dagPath()).parent(0) 1613 validParent = DagNode(worldobj) 1614 # END if no valid parent 1615 1616 parentDagFn = api.MFnDagNode(validParent.dagPath()) 1617 childNodeObject = childNode.object() 1618 1619 docmd = Call(parentDagFn.removeChild, childNodeObject) 1620 # TODO: find current position of item at parent restore it exactly 1621 undocmdCall = Call(parentDagFn.addChild, childNodeObject, MFnDagNode.kNextPos, True) # call ourselves 1622 1623 # special case to add items back to world 1624 op.addCmd(docmd, undocmdCall) 1625 # END if not keep existing parent 1626 1627 op.doIt() 1628 1629 # UPDATE THE DAG PATH OF CHILDNODE 1630 ################################ 1631 # find dag path at the used index 1632 dagIndex = pos 1633 if pos == self.kNextPos: 1634 dagIndex = self.childCount() - 1 # last entry as child got added 1635 newChildNode = NodeFromObj(MDagPathUtil.childPathAtIndex(self.dagPath(), dagIndex)) 1636 1637 # update undo cmd to use the newly created child with the respective dag path 1638 undocmd.args = [newChildNode] 1639 1640 # ALTER CMD FOR WORLD SPECIAL CASE ? 1641 ###################################### 1642 # alter undo to readd childNode to world using MEL ? - need final name for 1643 # this, which is why we delay so much 1644 if not keepExistingParent and not parentTransform and undocmdCall is not None: # have call and child is under world 1645 undocmdCall.func = cmds.parent 1646 undocmdCall.args = [str(newChildNode)] 1647 undocmdCall.kwargs = { "add":1, "world":1 } 1648 1649 return newChildNode
1650 1651 @undoable
1652 - def addParent(self, parentnode, **kwargs):
1653 """Adds ourselves as instance to the given parentnode at position 1654 1655 :param kwargs: see `addChild` 1656 :return: self with updated dag path""" 1657 kwargs.pop("keepExistingParent", None) 1658 return parentnode.addChild(self, keepExistingParent = True, **kwargs)
1659 1660 @undoable
1661 - def setParent(self, parentnode, **kwargs):
1662 """Change the parent of self to parentnode being placed at position 1663 1664 :param kwargs: see `addChild` 1665 :return: self with updated dag path""" 1666 kwargs.pop("keepExistingParent", None) # knock off our changed attr 1667 return parentnode.addChild(self, keepExistingParent = False, **kwargs)
1668 1669 @undoable
1670 - def removeParent(self, parentnode ):
1671 """Remove ourselves from given parentnode 1672 1673 :return: None""" 1674 return parentnode.removeChild(self)
1675 1676 1677 #} END DAG modification 1678 1679 @undoable
1680 - def delete(self):
1681 """Delete this node - this special version must be 1682 1683 :note: if the undo queue is enabled, the object becomes invalid, but stays alive until it 1684 drops off the queue 1685 :note: if you want to delete many nodes, its more efficient to delete them 1686 using the global `delete` method""" 1687 mod = undo.DagModifier() 1688 mod.deleteNode(self.object()) 1689 mod.doIt()
1690 1691 #{ Edit 1692 1693 @notundoable
1694 - def duplicate(self, newpath='', autocreateNamespace=True, renameOnClash=True, 1695 newTransform = False, **kwargs):
1696 """Duplciate the given node to newpath 1697 1698 :param newpath: result depends on its format: 1699 1700 * '' - empty string, creates a unique name based on the actual node name by appending a copy number 1701 to it, if newTransform is True, the newly created shape/transform will keep its name, but receives a new parent 1702 * 'newname' - relative path, the node will be duplicated not changing its current parent if newTransform is False 1703 * ``|parent|newname`` - absolute path, the node will be duplicated and reparented under the given path 1704 if newTransform is True, a new transform name will be created based on your name by appending a unique copy number 1705 1706 :param autocreateNamespace: if true, namespaces given in newpath will be created automatically, otherwise 1707 a RuntimeException will be thrown if a required namespace does not exist 1708 :param renameOnClash: if true, clashing names will automatically be resolved by adjusting the name 1709 :param newTransform: if True, a new transform will be created based on the name of the parent transform 1710 of this shape node, appending a unique copy number to it. 1711 Only has an effect for shape nodes 1712 :return: newly create Node 1713 :note: duplicate performance could be improved by checking more before doing work that does not 1714 really change the scene, but adds undo operations 1715 :note: inbetween parents are always required as needed 1716 :todo: add example for each version of newpath 1717 :note: instancing can be realized using the `addChild` function 1718 :note: If meshes have tweaks applied, the duplicate will not have these tweaks and the meshes will look 1719 mislocated. 1720 Using MEL works in that case ... (they fixed it there obviously) , but creates invalid objects 1721 :todo: Undo implementation - every undoable operation must in fact be based on strings to really work, all 1722 this is far too much - dagNode.duplicate must be undoable by itself 1723 :todo: duplicate should be completely reimplemented to support all mel options and actually work with 1724 meshes and tweaks - the underlying api duplication would still be used of course, as well as 1725 connections (to sets) and so on ... """ 1726 selfIsShape = isinstance(self, Shape) 1727 1728 # NAME HANDLING 1729 # create a valid absolute name to have less special cases later on 1730 # if there is no name given, create a name 1731 if not newpath: # "" or None 1732 if newTransform: 1733 newpath = "%s|%s" % (_getUniqueName(self.transform()), self.basename()) 1734 else: 1735 newpath = _getUniqueName(self) 1736 # END newTransform if there is no new path given 1737 elif newTransform and selfIsShape: 1738 newpath = "%s|%s" % (_getUniqueName(self.transform()), newpath.split('|')[-1]) 1739 elif '|' not in newpath: 1740 myparent = self.parent() 1741 parentname = "" 1742 if myparent is not None: 1743 parentname = str(myparent) 1744 newpath = "%s|%s" % (parentname, newpath) 1745 # END path name handling 1746 1747 # Instance Parent Check 1748 dagtokens = newpath.split('|') 1749 1750 1751 # ASSERT NAME 1752 ############# 1753 # need at least transform and shapename if path is absolute 1754 numtokens = 3 # like "|parent|shape" -> ['','parent', 'shape'] 1755 shouldbe = '|transformname|shapename' 1756 if not selfIsShape: 1757 numtokens = 2 # like "|parent" -> ['','parent'] 1758 shouldbe = '|transformname' 1759 1760 if '|' in newpath and (newpath == '|' or len(dagtokens) < numtokens): 1761 raise NameError("Duplicate paths should be at least %s, was %s" % (shouldbe, newpath)) 1762 # END not instance path checking 1763 1764 1765 # TARGET EXISTS ? 1766 ##################### 1767 if '|' in newpath and objExists(newpath): 1768 exnode = NodeFromStr(newpath) 1769 if not isinstance(exnode, self.__class__): 1770 raise RuntimeError("Existing object at path %s was of type %s, should be %s" 1771 % (newpath, exnode.__class__.__name__, self.__class__.__name__)) 1772 return exnode# return already existing one as it has a compatible type 1773 # END target exists check 1774 1775 1776 1777 # DUPLICATE IT WITHOUT UNDO 1778 ############################ 1779 # it will always duplicate the transform and return it 1780 # in case of instances, its the only way we have to get it below an own parent 1781 # bake all names into strings for undo and redo 1782 duplicate_node_parent = NodeFromObj(api.MFnDagNode(self.dagPath()).duplicate(False, False)) # get the duplicate 1783 1784 1785 # RENAME DUPLICATE CHILDREN 1786 ########################### 1787 # 1788 childsourceparent = self.transform() # works if we are a transform as well 1789 self_shape_duplicated = None # store Node of duplicates that corresponds to us (only if self is shape) 1790 1791 srcchildren = childsourceparent.childrenDeep() 1792 destchildren = duplicate_node_parent.childrenDeep() 1793 1794 1795 if len(srcchildren) != len(destchildren): 1796 # Happens if we have duplicated a shape, whose transform hat several shapes 1797 # To find usually, there should be only one shape which is our duplicated shape 1798 if len(destchildren) != 1: 1799 raise AssertionError("Expected %s to have exactly one child, but it had %i" % (duplicate_node_parent, len(destchildren))) 1800 self_shape_duplicated = destchildren[0].rename(self.basename()) 1801 else: 1802 # this is the only part where we have a one-one relationship between the original children 1803 # and their copies - store the id the current basename once we encounter it 1804 selfbasename = self.basename() 1805 for i,targetchild in enumerate(destchildren): 1806 srcchildbasename = srcchildren[i].basename() 1807 targetchild.rename(srcchildbasename) 1808 # HACK: we should only check the intermediate children, but actually conisder them deep 1809 # trying to reduce risk of problem by only setting duplicate_shape_index once 1810 if not self_shape_duplicated and selfbasename == srcchildbasename: 1811 self_shape_duplicated = targetchild 1812 # END for each child to rename 1813 # END CHILD RENAME 1814 1815 1816 # REPARENT 1817 ############### 1818 # create requested parents of our duplicate 1819 parenttokens = dagtokens[:-1] 1820 leafobjectname = dagtokens[-1] # the basename of the dagpath 1821 duplparentname = None 1822 if selfIsShape and newTransform: 1823 parenttokens = dagtokens[:-2] # the parent of the duplicate node parent transform 1824 duplparentname = dagtokens[-2] 1825 # END shape and new transform handling 1826 1827 1828 if parenttokens: # could be [''] too if newpath = '|newpath' 1829 parentnodepath = '|'.join(parenttokens) 1830 parentnode = childsourceparent # in case we have a relative name 1831 1832 # happens on input like "|name", 1833 # handle case that we are duplicating a transform and end up with a name 1834 # that already exists - createNode will return the existing one, and error if 1835 # the type does not match 1836 # We have to keep the duplicate as it contains duplicated values that are not 1837 # present in a generic newly created transform node 1838 parentnode = None 1839 if cmds.objExists(parentnodepath): 1840 parentnode = NodeFromStr(parentnodepath) 1841 elif parentnodepath != '': 1842 parentnode = createNode(parentnodepath, "transform", 1843 renameOnClash=renameOnClash, 1844 autocreateNamespace=autocreateNamespace) 1845 # END create parent handling 1846 1847 1848 # reparent our own duplicated node - this is always a transform at this 1849 # point 1850 if parentnode is not None: 1851 if selfIsShape and not newTransform: 1852 # duplicate_shape_parent is not needed, reparent shape to our valid parent 1853 # name and remove the intermediate parent 1854 self_shape_duplicated = self_shape_duplicated.reparent(parentnode, renameOnClash = renameOnClash) 1855 if str(duplicate_node_parent) not in str(parentnode): 1856 duplicate_node_parent.delete() 1857 duplicate_node_parent = None 1858 # END if we may delete the duplicate node parent 1859 # END self is shape 1860 else: 1861 # we are a transform and will reparent under our destined parent node 1862 duplicate_node_parent = duplicate_node_parent.reparent(parentnode, renameOnClash=renameOnClash) 1863 # END if there is a new parent node 1864 # END PARENT HANDLING 1865 1866 # if we are a shape duplication, we have to rename the duplicated parent node as well 1867 # since maya's duplication routine really does a lot to change my names :) 1868 if duplparentname and duplicate_node_parent is not None: 1869 duplicate_node_parent = duplicate_node_parent.rename(duplparentname, renameOnClash=renameOnClash) 1870 # END dupl parent rename 1871 1872 # FIND RETURN NODE 1873 ###################### 1874 final_node = rename_target = duplicate_node_parent # item that is to be renamed to the final name later 1875 1876 # rename target must be the child matching our name 1877 if selfIsShape: # want shape, have transform 1878 final_node = rename_target = self_shape_duplicated 1879 1880 1881 # RENAME TARGET 1882 # rename the target to match the leaf of the path 1883 # we currently do not check whether the name is already set 1884 # - the rename method does that for us 1885 final_node = rename_target.rename(leafobjectname, autocreateNamespace = autocreateNamespace, 1886 renameOnClash=renameOnClash) 1887 1888 # call our base class to copy additional information 1889 self.copyTo(final_node, **kwargs) 1890 return final_node
1891 1892 #} END edit 1893 1894 1895 #{ DAG Status Information
1896 - def _checkHierarchyVal(self, plugName, cmpval):
1897 """ 1898 :return: cmpval if the plug value of one of the parents equals cmpval 1899 as well as the current entity""" 1900 if getattr(self, plugName).asInt() == cmpval: 1901 return cmpval 1902 1903 for parent in self.iterParents(): 1904 if getattr(parent, plugName).asInt() == cmpval: 1905 return cmpval 1906 1907 return 1 - cmpval
1908
1909 - def _getDisplayOverrideValue(self, plugName):
1910 """ 1911 :return: the given effective display override value or None if display 1912 overrides are disabled""" 1913 if self.do.mchildByName('ove').asInt(): 1914 return getattr(self.do, plugName).asInt() 1915 1916 for parent in self.iterParents(): 1917 if parent.do.mchildByName('ove').asInt(): 1918 return parent.do.mchildByName(plugName).asInt() 1919 1920 return None
1921
1922 - def isVisible(self):
1923 """ 1924 :return: True if this node is visible - its visible if itself and all parents are 1925 visible""" 1926 return self._checkHierarchyVal('v', False)
1927
1928 - def isTemplate(self):
1929 """ 1930 :return: True if this node is templated - this is the case if itself or one of its 1931 parents are templated """ 1932 return self._checkHierarchyVal('tmp', True)
1933
1934 - def displayOverrideValue(self, plugName):
1935 """ 1936 :return: the override display value actually identified by plugName affecting 1937 the given object (that should be a leaf node for the result you see in the viewport. 1938 The display type in effect is always the last one set in the hierarchy 1939 returns None display overrides are disabled""" 1940 return self._getDisplayOverrideValue(plugName)
1941 #} END dag status information 1942
1943 - def isValid(self):
1944 """:return: True if the object exists in the scene 1945 :note: Handles DAG objects correctly that can be instanced, in which case 1946 the MObject may be valid , but the respective dag path is not. 1947 Additionally, if the object is not parented below any object, everything appears 1948 to be valid, but the path name is empty """ 1949 return self.dagPath().isValid() and self.dagPath().fullPathName() != '' and DependNode.isValid(self)
1950
1951 - def name(self):
1952 """:return: fully qualified (long) name of this dag node""" 1953 return self.fullPathName()
1954 1955 # override dependnode implementation with the original one 1956 basename = iDagItem.basename 1957 1958 1959 #{ DAG Query 1960
1961 - def parentAtIndex(self, index):
1962 """:return: Node of the parent at the given index - non-instanced nodes only have one parent 1963 :note: if a node is instanced, it can have `parentCount` parents 1964 :todo: Update dagpath afterwards ! Use dagpaths instead !""" 1965 sutil = api.MScriptUtil() 1966 sutil.createFromInt(index) 1967 uint = sutil.asUint() 1968 1969 return NodeFromObj(api.MFnDagNode(self.dagPath()).parent(uint))
1970
1971 - def transform(self):
1972 """:return: Node to lowest transform in the path attached to our node 1973 :note: for shapes this is the parent, for transforms the transform itself""" 1974 # this should be faster than asking maya for the path and converting 1975 # back to a Node 1976 if isinstance(self, Transform): 1977 return self 1978 return NodeFromObj(self.dagPath().transform())
1979
1980 - def parent(self):
1981 """:return: Maya node of the parent of this instance or None if this is the root""" 1982 # implement raw not using a wrapped path 1983 copy = MDagPath(self.dagPath()) 1984 copy.pop(1) 1985 if copy.length() == 0: # ignore world ! 1986 return None 1987 return NodeFromObj(copy)
1988
1989 - def children(self, predicate = lambda x: True, asNode=True):
1990 """:return: all child nodes below this dag node if predicate returns True for passed Node 1991 :param asNode: if True, you will receive the children as wrapped Nodes, otherwise you 1992 get MDagPaths""" 1993 out = list() 1994 ownpath = self.dagPath() 1995 for i in range(ownpath.childCount()): 1996 copy = MDagPath(ownpath) 1997 copy.push(MDagPath.child(ownpath, i)) 1998 1999 if asNode: 2000 copy = NodeFromObj(copy) 2001 2002 if not predicate(copy): 2003 continue 2004 2005 out.append(copy) 2006 # END for each child 2007 return out
2008
2009 - def childrenByType(self, nodeType, predicate = lambda x: True):
2010 """:return: all childnodes below this one matching the given nodeType and the predicate 2011 :param nodeType: class of the nodeTyoe, like nt.Transform""" 2012 return [p for p in self.children() if isinstance(p, nodeType) and predicate(p)]
2013
2014 - def shapes(self, predicate = lambda x: True):
2015 """:return: all our Shape nodes 2016 :note: you could use getChildren with a predicate, but this method is more 2017 efficient as it uses dagpath functions to filter shapes""" 2018 shapeNodes = map(NodeFromObj, MDagPathUtil.shapes(self.dagPath())) # could use getChildrenByType, but this is faster 2019 return [s for s in shapeNodes if predicate(s)]
2020
2021 - def childTransforms(self, predicate = lambda x: True):
2022 """:return: list of all transform nodes below this one """ 2023 transformNodes = map(NodeFromObj, MDagPathUtil.transforms(self.dagPath())) # could use getChildrenByType, but this is faster 2024 return [t for t in transformNodes if predicate(t)]
2025
2026 - def instanceNumber(self):
2027 """:return: our instance number 2028 :note: 0 does not indicate that this object is not instanced - use getInstanceCount instead""" 2029 return self.dagPath().instanceNumber()
2030
2031 - def instance(self, instanceNumber):
2032 """:return: Node to the instance identified by instanceNumber 2033 :param instanceNumber: range(0, self.instanceCount()-1)""" 2034 # secure it - could crash if its not an instanced node 2035 if self.instanceCount(False) == 1: 2036 if instanceNumber: 2037 raise AssertionError("instanceNumber for non-instanced nodes must be 0, was %i" % instanceNumber) 2038 return self 2039 2040 allpaths = api.MDagPathArray() 2041 self.getAllPaths(allpaths) 2042 # copy the path as it will be invalidated once the array goes out of scope ! 2043 return NodeFromObj(MDagPath(allpaths[instanceNumber]))
2044
2045 - def hasChild(self, node):
2046 """:return: True if node is a child of self""" 2047 return api.MFnDagNode(self.dagPath()).hasChild(node.object())
2048
2049 - def child(self, index):
2050 """:return: child of self at index 2051 :note: this method fixes the MFnDagNode.child method - it returns an MObject, 2052 which doesnt work well with instanced nodes - a dag path is required, which is what 2053 we use to aquire the object""" 2054 copy = MDagPath(self.dagPath()) 2055 copy.push(MDagPath.child(self.dagPath(), index)) 2056 return NodeFromObj(copy)
2057 2058 #} END dag query 2059 2060
2061 - def _dagPath_delayed(self):
2062 """Handles the retrieval of a dagpath from an MObject if it is not known 2063 at first.""" 2064 self._apidagpath = MDagPath() 2065 _mfndag_setObject(self._apiobj) 2066 _mfndag.getPath(self._apidagpath) 2067 cls = type(self) 2068 object.__setattr__(self, 'dagPath', instancemethod(cls._dagPath_cached, self, cls)) 2069 return self._apidagpath
2070
2071 - def _dagPath_cached(self):
2072 """:return: MDagPath attached to this node from a cached location""" 2073 return self._apidagpath
2074
2075 - def _object_cached(self):
2076 """:return: MObject associated with the path of this instance from a cached location""" 2077 return self._apiobj
2078
2079 - def _object_delayed(self):
2080 """:return: MObject as retrieved from the MDagPath of our Node""" 2081 self._apiobj = self._apidagpath.node() # expensive call 2082 cls = type(self) 2083 object.__setattr__(self, 'object', instancemethod(cls._object_cached, self, cls)) 2084 return self._apiobj
2085 2086 # delayed mobject retrieval is the default for DagNodes as they are created from 2087 # MDagPaths most of the time 2088 object = _object_delayed 2089
2090 - def dagPath(self):
2091 """ 2092 :return: the original DagPath attached to this Node - it's not wrapped 2093 for performance 2094 :note: If you plan to alter it, make sure you copy it using the 2095 MDagPath(node.dagPath()) construct !""" 2096 return self._apidagpath
2097
2098 - def apiObject(self):
2099 """:return: our dag path as this is our api object - the object defining this node best""" 2100 return self.dagPath()
2101 2102 2103 #{ Iterators
2104 - def iterInstances(self, excludeSelf = False):
2105 """Get iterator over all (direct and indirect)instances of this node 2106 2107 :param excludeSelf: if True, self will not be returned, if False, it will be in 2108 the list of items 2109 :note: Iterating instances is more efficient than querying all instances individually using 2110 `instance` 2111 :todo: add flag to allow iteration of indirect instances as well """ 2112 # prevents crashes if this method is called within a dag instance added callback 2113 if self.instanceCount(True) == 1: 2114 if not excludeSelf: 2115 yield self 2116 raise StopIteration 2117 2118 ownNumber = -1 2119 if excludeSelf: 2120 ownNumber = self.instanceNumber() 2121 2122 allpaths = api.MDagPathArray() 2123 self.getAllPaths(allpaths) 2124 2125 # paths are ordered by instance number 2126 for i in range(allpaths.length()): 2127 # index is NOT instance number ! If transforms are instanced, children increase instance number 2128 dagpath = allpaths[i] 2129 if dagpath.instanceNumber() != ownNumber: 2130 yield NodeFromObj(MDagPath(dagpath))
2131 # END for each instance
2132 2133 #} END iterators 2134 2135 #} END base (classes) 2136 2137 #{ Attributes 2138 2139 -def _new_mixin(cls, *args, **kwargs):
2140 """Constructor for MObject derived types which only differ in a few parameters. 2141 Requires _base_cls_ and _mfn_suffix_ to be set on the respective class 2142 2143 return an attribute class of the respective type for given MObject 2144 2145 :param args: arg[0] is attribute's MObject to be wrapped. 2146 :note: Custom constructors are not possible as __init__ is automatically called 2147 afterwards - MObject does not support anything but no args or another MObject.""" 2148 # may fail as we didn't check of len(args), but its okay, lets safe the if statement 2149 # here ! Python will bark nicely anyway 2150 mobject = args[0] 2151 if cls != cls._base_cls_: 2152 # the user knows which type he wants, created it directly 2153 newinst = object.__new__(cls, mobject) 2154 # NOTE: Although this class is implemented not to need the _apiobj anymore 2155 # as we ARE an MObject, we are learning from the issue in Component 2156 # and just keep another reference to it, to be on the safe side 2157 # DEL_ME_AND_CRASH ############################ 2158 newinst._apiobj = newinst ######## 2159 ################################# 2160 return newinst 2161 # END optimization 2162 newinst = _createInstByPredicate(mobject, cls, cls, lambda x: x.endswith(cls._mfn_suffix_)) 2163 2164 if newinst is None: 2165 raise ValueError("%s with apitype %r could not be wrapped into any function set" % (cls._mfn_suffix_, mobject.apiTypeStr())) 2166 2167 return newinst
2168 2169 # assure proper name, just in case 2170 _new_mixin.__name__ = '__new__'
2171 2172 -class Attribute(MObject):
2173 """Represents an attribute in general - this is the base class 2174 Use this general class to create attribute wraps - it will return 2175 a class of the respective type """ 2176 2177 __metaclass__ = MetaClassCreatorNodes 2178 _base_cls_ = None 2179 _mfn_suffix_ = 'Attribute' 2180 2181 __new__ = _new_mixin 2182 2183 @classmethod
2184 - def create(cls, full_name, brief_name, *args, **kwargs):
2185 """:return: A new Attribute 2186 :param full_name: the long name of the attribute 2187 :param brief_name: the brief name of the attribute 2188 :note: all args and kwargs are passed to the respective function set instance 2189 :note: specialize this method in derived types if required""" 2190 if cls == Attribute: 2191 raise TypeError("Cannot create plain Attributes, choose a subclass of Attribute instead") 2192 # END handle invalid type 2193 2194 # keep the class around to be sure we don't die on the way due to decremented 2195 # ref counts 2196 mfninst = cls._mfncls() 2197 attr = mfninst.create(full_name, brief_name, *args, **kwargs) 2198 return cls(attr) # this copies the MObject and we are safe
2199 2200 2201 Attribute._base_cls_ = Attribute
2202 2203 -class UnitAttribute(Attribute):
2204 pass
2205
2206 2207 -class TypedAttribute(Attribute):
2208 pass
2209
2210 2211 -class NumericAttribute(Attribute):
2212 @classmethod
2213 - def _create_using(cls, method_name, *args):
2214 mfninst = cls._mfncls() 2215 attr = getattr(mfninst, method_name)(*args) 2216 return cls(attr)
2217 2218 @classmethod
2219 - def createColor(cls, full_name, brief_name):
2220 """:return: An attribute representing a RGB color 2221 :param full_name: see `create` 2222 :param brief_name: see `create`""" 2223 return cls._create_using('createColor', full_name, brief_name)
2224 2225 @classmethod
2226 - def createPoint(cls, full_name, brief_name):
2227 """:return: An attribute representing a point with XYZ coordinates 2228 :param full_name: see `create` 2229 :param brief_name: see `create`""" 2230 return cls._create_using('createPoint', full_name, brief_name)
2231
2232 2233 -class MessageAttribute(Attribute):
2234 pass
2235
2236 2237 -class MatrixAttribute(Attribute):
2238 pass
2239
2240 2241 -class LightDataAttribute(Attribute):
2242 pass
2243
2244 2245 -class GenericAttribute(Attribute):
2246 pass
2247
2248 2249 -class EnumAttribute(Attribute):
2250 pass
2251
2252 2253 -class CompoundAttribute(Attribute):
2254 pass
2255
2256 #} END attributes 2257 2258 2259 #{ Data 2260 2261 -class Data(MObject):
2262 """Represents an data in general - this is the base class 2263 Use this general class to create data wrap objects - it will return a class of the respective type """ 2264 2265 __metaclass__ = MetaClassCreatorNodes 2266 _base_cls_ = None 2267 _mfn_suffix_ = 'Data' 2268 2269 __new__ = _new_mixin 2270 2271 @classmethod
2272 - def create(cls, *args, **kwargs):
2273 """:return: A new instance of data wrapped in the desired Data type 2274 :note: specialize this method in derived types !""" 2275 if cls == Data: 2276 raise TypeError("Cannot create 'plain' data, choose a subclass of Data instead") 2277 # END handle invalid type 2278 2279 # keep the instance alive until we have wrapped the MObject which essentiall 2280 # creates a copy and increments its maya ref count. 2281 mfninst = cls._mfncls() 2282 data = mfninst.create(*args, **kwargs) 2283 return cls(data)
2284 2285 Data._base_cls_ = Data
2286 2287 2288 -class VectorArrayData(Data):
2289 pass
2290
2291 2292 -class UInt64ArrayData(Data):
2293 pass
2294
2295 2296 -class StringData(Data):
2297 pass
2298
2299 2300 -class StringArrayData(Data):
2301 pass
2302
2303 2304 -class SphereData(Data):
2305 pass
2306
2307 2308 -class PointArrayData(Data):
2309 pass
2310
2311 2312 -class PluginData(Data):
2313 """Wraps plugin data as received by a plug. If plugin's registered their data 2314 types and tracking dictionaries using the `registerPluginDataTrackingDict`, 2315 the original self pointer can easily be retrieved using this classes interface""" 2316 2317
2318 - def data(self):
2319 """:return: python data wrapped by this plugin data object 2320 :note: the python data should be made such that it can be changed using 2321 the reference we return - otherwise it will be read-only as it is just a copy ! 2322 :note: the data retrieved by this method cannot be used in plug.msetMObject(data) as it 2323 is ordinary python data, not an mobject 2324 :raise RuntimeError: if the data object's id is unknown to this class""" 2325 import maya.OpenMayaMPx as mpx # delayed import as it takes plenty of time 2326 2327 mfn = self._mfncls(self._apiobj) 2328 datatype = mfn.typeId() 2329 try: 2330 trackingdict = sys._dataTypeIdToTrackingDictMap[datatype.id()] 2331 except KeyError: 2332 raise RuntimeError("Datatype %r is not registered to python as plugin data" % datatype) 2333 else: 2334 # retrieve the data pointer 2335 dataptrkey = mpx.asHashable(mfn.data()) 2336 try: 2337 return trackingdict[dataptrkey] 2338 except KeyError: 2339 raise RuntimeError("Could not find data associated with plugin data pointer at %r" % dataptrkey)
2340 # END exception handling tracking dict
2341 # END exception handling dict access 2342 2343 2344 -class NumericData(Data):
2345 pass
2346
2347 2348 -class NObjectData(Data):
2349 pass
2350
2351 2352 -class NIdData(Data):
2353 """:note: maya 2011 and newer""" 2354 pass
2355
2356 2357 -class MatrixData(Data):
2358 pass
2359
2360 2361 -class IntArrayData(Data):
2362 pass
2363
2364 2365 -class GeometryData(Data):
2366 """Wraps geometry data providing additional convenience methods""" 2367
2368 - def uniqueObjectId(self):
2369 """:return: an object id that is guaranteed to be unique 2370 :note: use it with addObjectGroup to create a new unique group""" 2371 # find a unique object group id 2372 objgrpid = 0 2373 for ogid in range(self.objectGroupCount()): 2374 exog = self.objectGroup(ogid) 2375 while exog == objgrpid: 2376 objgrpid += 1 2377 # END for each existing object group 2378 return objgrpid
2379
2380 2381 -class SubdData(GeometryData):
2382 pass
2383
2384 2385 -class NurbsSurfaceData(GeometryData):
2386 pass
2387
2388 2389 -class NurbsCurveData(GeometryData):
2390 pass
2391
2392 2393 -class MeshData(GeometryData):
2394 pass
2395
2396 2397 -class LatticeData(GeometryData):
2398 pass
2399
2400 2401 -class DynSweptGeometryData(Data):
2402 pass
2403
2404 2405 -class DoubleArrayData(Data):
2406 pass
2407
2408 2409 -class ComponentListData(Data):
2410 """Improves the default wrap by adding some required methods to deal with 2411 component lists""" 2412
2413 - def __getitem__(self, index):
2414 """:return: the item at the given index""" 2415 return self._mfncls(self)[index]
2416
2417 - def __len__(self):
2418 """:return: number of components stored in this data""" 2419 return self.length()
2420
2421 - def __contains__(self, component):
2422 """:return: True if the given component is contained in this data""" 2423 return self.has(component)
2424
2425 2426 -class ArrayAttrsData(Data):
2427 pass
2428
2429 #} END data 2430 2431 2432 #{ Components 2433 2434 -class Component(MObject):
2435 """Represents a shape component - its derivates can be used to handle component lists 2436 to be used in object sets and shading engines """ 2437 __metaclass__ = MetaClassCreatorNodes 2438 _mfnType = None # to be set in the subclass component 2439 _base_cls_ = None 2440 _mfn_suffix_ = "Component" 2441 2442 __new__ = _new_mixin 2443 2444 @classmethod
2445 - def create(cls, component_type):
2446 """:return: A new component instance carrying data of the given component type 2447 :param component_type: MFn:: component type to be created. 2448 :note: It is important that you call this function on the Component Class of 2449 a compatible type, or a RuntimeError will occour""" 2450 if cls == Component: 2451 raise TypeError("The base compnent type cannot be instantiated") 2452 # END handle invalid type 2453 2454 cdata = cls._mfncls().create(component_type) 2455 return cls(cdata)
2456 2457 @classmethod
2458 - def getMFnType(cls):
2459 """:return: mfn type of this class 2460 :note: the type returned is *not* the type of the shape component""" 2461 return cls._mfnType
2462
2463 - def addElements(self, *args):
2464 """Operates exactly as described in the MFn...IndexComponent documentation, 2465 but returns self to allow combined calls and on-the-fly component generation 2466 2467 :return: self""" 2468 self._mfncls(self).addElements(*args) 2469 return self
2470
2471 - def addElement(self, *args):
2472 """see `addElements` 2473 2474 :return: self 2475 :note: do not use this function as it will be really slow when handling many 2476 items, use addElements instead""" 2477 self._mfncls(self).addElement(*args) 2478 return self
2479 2480 Component._base_cls_ = Component
2481 2482 -class SingleIndexedComponent(Component):
2483 """precreated class for ease-of-use""" 2484 _mfnType = api.MFn.kSingleIndexedComponent 2485
2486 - def getElements(self):
2487 """:return: MIntArray containing the indices this component represents""" 2488 u = api.MIntArray() 2489 api.MFnSingleIndexedComponent(self).getElements(u) 2490 return u
2491 2492 # aliases 2493 elements = getElements
2494
2495 -class DoubleIndexedComponent(Component): # derived just for epydoc
2496 """Fixes some functions that would not work usually """ 2497 _mfnType = api.MFn.kDoubleIndexedComponent 2498
2499 - def getElements(self):
2500 """ 2501 :return: (uIntArray, vIntArray) tuple containing arrays with the u and v 2502 indices this component represents""" 2503 u = api.MIntArray() 2504 v = api.MIntArray() 2505 api.MFnDoubleIndexedComponent(self).getElements(u, v) 2506 return (u,v)
2507 2508 # aliases 2509 elements = getElements 2510
2511 2512 -class TripleIndexedComponent(Component):
2513 """precreated class for ease-of-use""" 2514 _mfnType = api.MFn.kTripleIndexedComponent 2515
2516 - def getElements(self):
2517 """ 2518 :return: (uIntArray, vIntArray, wIntArray) tuple containing arrays with 2519 the u, v and w indices this component represents""" 2520 u = api.MIntArray() 2521 v = api.MIntArray() 2522 w = api.MIntArray() 2523 api.MFnDoubleIndexedComponent(self).getElements(u, v, w) 2524 return (u,v,w)
2525 2526 # aliases 2527 elements = getElements
2528
2529 #} END components 2530 2531 #{ Basic Types 2532 2533 -class MDagPathUtil(object):
2534 """Performs operations on MDagPaths which are hard or inconvenient to do otherwise 2535 2536 :note: We do NOT patch the actual api type as this would make it unusable to be passed in 2537 as reference/pointer type unless its being created by maya itself.""" 2538 2539 #{ Query 2540 2541 @classmethod
2542 - def parentPath(cls, path):
2543 """ 2544 :return: MDagPath to the parent of path or None if path is in the scene 2545 root.""" 2546 copy = MDagPath(path) 2547 copy.pop(1) 2548 if copy.length() == 0: # ignore world ! 2549 return None 2550 return copy
2551 2552 @classmethod
2553 - def numShapes(cls, path):
2554 """:return: return the number of shapes below path""" 2555 sutil = api.MScriptUtil() 2556 uintptr = sutil.asUintPtr() 2557 sutil.setUint(uintptr , 0) 2558 2559 path.numberOfShapesDirectlyBelow(uintptr) 2560 2561 return sutil.uint(uintptr)
2562 2563 @classmethod
2564 - def childPathAtIndex(cls, path, index):
2565 """:return: MDagPath pointing to this path's child at the given index""" 2566 copy = MDagPath(path) 2567 copy.push(path.child(index)) 2568 return copy
2569 2570 @classmethod
2571 - def childPaths(cls, path, predicate = lambda x: True):
2572 """:return: list of child MDagPaths which have path as parent 2573 :param predicate: returns True for each path which should be included in the result.""" 2574 outPaths = list() 2575 for i in xrange(path.childCount()): 2576 childpath = cls.childPathAtIndex(path, i) 2577 if predicate(childpath): 2578 outPaths.append(childpath) 2579 return outPaths
2580 2581 #} END query 2582 2583 #{ Edit Inplace 2584 @classmethod
2585 - def pop(cls, path, num):
2586 """Pop the given number of items off the end of the path 2587 2588 :return: path itself""" 2589 path.pop(num) 2590 return path
2591 2592 @classmethod
2593 - def extendToChild(cls, path, num):
2594 """Extend path to the given child number - can be shape or transform 2595 2596 :return: path itself""" 2597 path.extendToShapeDirectlyBelow(num) 2598 return self
2599 2600 @classmethod
2601 - def childPathsByFn(cls, path, fn, predicate = lambda x: True):
2602 """Get all children below path supporting the given MFn.type 2603 2604 :return: MDagPaths to all matched paths below this path 2605 :param fn: member of MFn 2606 :param predicate: returns True for each path which should be included in the result.""" 2607 isMatch = lambda p: p.hasFn(fn) 2608 return [p for p in cls.childPaths(path, predicate = isMatch) if predicate(p)]
2609 2610 @classmethod
2611 - def shapes(cls, path, predicate = lambda x: True):
2612 """:return: MDagPaths to all shapes below path 2613 :param predicate: returns True for each path which should be included in the result. 2614 :note: have to explicitly assure we do not get transforms that are compatible to the shape function 2615 set for some reason - this is just odd and shouldn't be, but it happens if a transform has an instanced 2616 shape for example, perhaps even if it is not instanced""" 2617 return [shape for shape in cls.childPathsByFn(path, api.MFn.kShape, predicate=predicate) if shape.apiType() != api.MFn.kTransform]
2618 2619 @classmethod
2620 - def transforms(cls, path, predicate = lambda x: True):
2621 """:return: MDagPaths to all transforms below path 2622 :param predicate: returns True to include path in result""" 2623 return cls.childPathsByFn(path, api.MFn.kTransform, predicate=predicate)
2624 #} END edit in place
2625 2626 2627 #} END basic types 2628 2629 #{ Default Types 2630 2631 -class Reference(DependNode):
2632 """Implements additional utilities to work with references""" 2633
2634 - def fileReference(self):
2635 """ 2636 :return: `FileReference` instance initialized with the reference we 2637 represent""" 2638 import mrv.maya.ref as refmod 2639 return refmod.FileReference(refnode=self)
2640
2641 2642 -class Transform(DagNode): # derived just for epydoc
2643 """Precreated class to allow isinstance checking against their types and 2644 to add undo support to MFnTransform functions, as well as for usability 2645 2646 :note: bases determined by metaclass 2647 :note: to have undoable set* functions , get the (improved) transformation matrix 2648 make your changes to it and use the `set` method """ 2649 2650 #{ MFnTransform Overrides 2651 2652 @undoable
2653 - def set(self, transformation):
2654 """Set the transformation of this Transform node""" 2655 curtransformation = self.transformation() 2656 setter = self._api_set 2657 op = undo.GenericOperation() 2658 op.setDoitCmd(setter, transformation) 2659 op.setUndoitCmd(setter, curtransformation) 2660 op.doIt()
2661 2662 #} END mfntransform overrides 2663 2664 2665 #{ Convenience Overrides
2666 - def getScale(self):
2667 """:return: MVector containing the scale of the transform""" 2668 return in_double3_out_vector(self._api_getScale)
2669
2670 - def getShear(self):
2671 """:return: MVector containing the shear of the transform""" 2672 return in_double3_out_vector(self._api_getShear)
2673 2674 @undoable
2675 - def setScale(self, vec_scale):
2676 """Set the scale of the transform with undo support from a single vector""" 2677 return undoable_in_double3_as_vector(self._api_setScale, self.getScale(), vec_scale)
2678 2679 @undoable
2680 - def setShear(self, vec_shear):
2681 """Set the shear value of the transform with undo support from single vector""" 2682 return undoable_in_double3_as_vector(self._api_setShear, self.getShear(), vec_shear)
2683 2684 @undoable
2685 - def shearBy(self, vec_value):
2686 """Add the given vector to the transform's shear""" 2687 return undoable_in_double3_as_vector(self._api_shearBy, self.getShear(), vec_value)
2688 2689 @undoable
2690 - def scaleBy(self, vec_value):
2691 """Add the given vector to the transform's scale""" 2692 return undoable_in_double3_as_vector(self._api_scaleBy, self.getScale(), vec_value)
2693
2694 #} END convenience overrides 2695 2696 2697 -class Shape(DagNode): # base for epydoc !
2698 """Interface providing common methods to all geometry shapes as they can be shaded. 2699 They usually support per object and per component shader assignments 2700 2701 :note: as shadingEngines are derived from objectSet, this class deliberatly uses 2702 them interchangably when it comes to set handling. 2703 :note: for convenience, this class implements the shader related methods 2704 whereever possible 2705 :note: bases determined by metaclass""" 2706 2707 2708 #{ preset type filters 2709 fSetsRenderable = SetFilter(api.MFn.kShadingEngine, False, 0) # shading engines only 2710 fSetsDeformer = SetFilter(api.MFn.kSet, True , 1) # deformer sets only 2711 #} END type filters 2712 2713 #{ Sets Interface 2714
2715 - def _parseSetConnections(self, allow_compoents):
2716 """Manually parses the set connections from self 2717 2718 :return: tuple(MObjectArray(setapiobj), MObjectArray(compapiobj)) if allow_compoents, otherwise 2719 just a list(setapiobj)""" 2720 sets = api.MObjectArray() 2721 iogplug = self._getSetPlug() # from DagNode , usually iog plug 2722 2723 # this will never fail - logcical index creates the plug as needed 2724 # and drops it if it is no longer required 2725 if allow_compoents: 2726 components = api.MObjectArray() 2727 2728 # take full assignments as well - make it work as the connectedSets api method 2729 for dplug in iogplug.moutputs(): 2730 sets.append(dplug.node()) 2731 components.append(MObject()) 2732 # END full objecft assignments 2733 2734 for compplug in iogplug.mchildByName('objectGroups'): 2735 for setplug in compplug.moutputs(): 2736 sets.append(setplug.node()) # connected set 2737 2738 # get the component from the data 2739 compdata = compplug.mchildByName('objectGrpCompList').masData() 2740 if compdata.length() == 1: # this is what we can handle 2741 components.append(compdata[0]) # the component itself 2742 else: 2743 raise AssertionError("more than one compoents in list") 2744 # END assure we have components in data 2745 # END for each set connected to component 2746 # END for each component group 2747 2748 return (sets, components) 2749 else: 2750 for dplug in iogplug.moutputs(): 2751 sets.append(dplug.node()) 2752 return sets
2753 # END for each object grouop connection in iog 2754 2755
2756 - def componentAssignments(self, setFilter = fSetsRenderable, use_api = True, asComponent = True):
2757 """ 2758 :return: list of tuples(ObjectSetNode, Component_or_MObject) defininmg shader 2759 assignments on per component basis. 2760 2761 If a shader is assigned to the whole object, the component would be a null object, otherwise 2762 it is an instance of a wrapped IndexedComponent class 2763 :note: The returned Component will be an MObject(kNullObject) only in case the component is 2764 not set. Hence you should check whether it isNull() before actually using it. 2765 :param setFilter: see `connectedSets` 2766 :param use_api: if True, api methods will be used if possible which is usually faster. 2767 If False, a custom non-api implementation will be used instead. 2768 This can be required if the apiImplementation is not reliable which happens in 2769 few cases of 'weird' component assignments 2770 :param asComponent: If True, the components will be wrapped into the matching MRV compontent type 2771 to provide a nicer interface. This might slightly slow down the process, but this is usually 2772 neglectable. 2773 :note: the sets order will be the order of connections of the respective component list 2774 attributes at instObjGroups.objectGroups 2775 :note: currently only meshes and subdees support per component assignment, whereas only 2776 meshes can have per component shader assignments 2777 :note: SubDivision Components cannot be supported as the component type kSubdivCVComponent 2778 cannot be wrapped into any component function set - reevaluate that with new maya versions ! 2779 :note: deformer set component assignments are only returned for instance 0 ! They apply to all 2780 output meshes though""" 2781 # SUBDEE SPECIAL CASE 2782 ######################### 2783 # cannot handle components for subdees - return them empty 2784 if self._apiobj.apiType() == api.MFn.kSubdiv: 2785 log.warn("components are not supported for Subdivision surfaces due to m8.5 api limitation") 2786 sets = self.connectedSets(setFilter = setFilter) 2787 return [(setnode, MObject()) for setnode in sets] 2788 # END subdee handling 2789 2790 sets = components = None 2791 2792 # MESHES AND NURBS 2793 ################## 2794 # QUERY SETS AND COMPONENTS 2795 # for non-meshes, we have to parse the components manually 2796 if not use_api or not self._apiobj.hasFn(api.MFn.kMesh) or not self.isValidMesh(): 2797 # check full membership 2798 sets,components = self._parseSetConnections(True) 2799 # END non-mesh handling 2800 else: 2801 # MESH - use the function set 2802 # take all fSets by default, we do the filtering 2803 sets = api.MObjectArray() 2804 components = api.MObjectArray() 2805 self.getConnectedSetsAndMembers(self.instanceNumber(), sets, components, False) 2806 # END sets/components query 2807 2808 2809 # wrap the sets and components 2810 outlist = list() 2811 for setobj,compobj in zip(sets, components): 2812 if not setFilter(setobj): 2813 continue 2814 2815 setobj = NodeFromObj(MObject(setobj)) # copy obj to get memory to python 2816 if not compobj.isNull() and asComponent: 2817 compobj = Component(compobj) # this copies the object as well 2818 else: 2819 compobj = MObject(compobj) # make it ours 2820 # END handle component type 2821 2822 outlist.append((setobj, compobj)) 2823 # END for each set/component pair 2824 return outlist
2825 2826 #} END set interface 2827 #} END default types 2828