1
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
25 from maya.OpenMaya import MFnDagNode, MDagPath, MObject, MObjectHandle
26
27 from itertools import chain
28 import sys
29
30 _nodesdict = None
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
51
52
53 _nameToApiSelList = api.MSelectionList()
54 _mfndep = api.MFnDependencyNode()
55 _mfndag = api.MFnDagNode()
56
57
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()
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")))
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
117
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
122
123 if isinstance(nodeTypeCls, StandinClass):
124 nodeTypeCls = nodeTypeCls.createCls()
125
126 return nodeTypeCls
127
130
131
132
133 if nodename.count('|') == 0:
134 return '|' + nodename
135 return nodename
136
138 return nodename.startswith('|')
139
141 """Find ONE valid dag path to the given dag api object"""
142 dagpath = MDagPath()
143 MFnDagNode(apiobj).getPath(dagpath)
144 return dagpath
145
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:
164 _nameToApiSelList.add(name)
165 except:
166 continue
167 else:
168 obj = MObject()
169 _nameToApiSelList.getDependNode(0, obj)
170
171
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
176
177 return obj
178
179
180 return None
181
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:
195 objnamelist.append(nodename[1:])
196
197 for name in objnamelist:
198 try:
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
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
216
217 return obj
218
219
220 return None
221
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):
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:
239
240
241 sellist.add(node)
242
243 return sellist
244
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):
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
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
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
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
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
297
298
299
300
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
331
332
333
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
344
345
346 dagnodes.sort(key = lambda n: len(str(n).split('|')), reverse = True)
347
348
349 nodes = chain(dagnodes, depnodes)
350
351
352
353
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
363
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
374 """:return: MSelectionList of the current selection list"""
375 sellist = api.MSelectionList()
376 api.MGlobal.getActiveSelectionList(sellist)
377 return sellist
378
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
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
409
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
461 if nodename[0] != '|':
462 nodename = "|" + nodename
463 subpaths.insert(0, '')
464 lenSubpaths += 1
465
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
475
476 do_existence_checks = True
477 dgmod = None
478 dagmod = None
479 for i in xrange(start_index, lenSubpaths):
480 nodepartialname = '|'.join(subpaths[0 : i+1])
481 is_last_iteration = i == lenSubpaths - 1
482
483
484
485
486
487 if do_existence_checks:
488 nodeapiobj = toApiobj(nodepartialname)
489 if nodeapiobj is not None:
490
491 if is_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
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
503
504 continue
505
506 else:
507
508 renameOnClash = True
509
510 else:
511
512 parentnode = createdNode = nodeapiobj
513 continue
514 else:
515 do_existence_checks = False
516
517
518
519
520
521
522 dagtoken = '|'.join(subpaths[i : i+1])
523
524 if autocreateNamespace:
525 nsm.createNamespace(":".join(dagtoken.split(":")[0:-1]))
526
527
528 actualtype = "transform"
529 if is_last_iteration:
530 actualtype = nodetype
531
532
533
534
535
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
541 newapiobj = None
542 if parentnode:
543 newapiobj = dagmod.createNode(actualtype, parentnode)
544 else:
545 newapiobj = dagmod.createNode(actualtype)
546
547 dagmod.renameNode(newapiobj, dagtoken)
548
549 parentnode = createdNode = newapiobj
550 else:
551 if dgmod is None:
552 dgmod = api.MDGModifier()
553
554
555
556
557
558 mod = dgmod
559 try:
560 newapiobj = dgmod.createNode(actualtype)
561 except RuntimeError:
562 if dagmod is None:
563 dagmod = api.MDagModifier()
564 mod = dagmod
565
566
567
568
569
570
571
572
573
574
575
576 if is_shape:
577 trans = dagmod.createNode("transform")
578 newapiobj = dagmod.createNode(actualtype, trans)
579 else:
580 newapiobj = dagmod.createNode(actualtype)
581
582
583 mod.renameNode(newapiobj, dagtoken)
584 createdNode = newapiobj
585
586
587
588
589
590 _mfndep_setobject(newapiobj)
591 actualname = _mfndep_name()
592 if actualname != dagtoken:
593
594
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
600 subpaths[i] = actualname
601 nodepartialname = '|'.join(subpaths[0 : i+1])
602
603
604
605
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
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
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
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
646
647 nodeTypeCls = nodeTypeToNodeTypeCls(typeName, apiobj)
648
649
650
651
652
653
654
655 if clsToBeCreated is not basecls and clsToBeCreated is not nodeTypeCls:
656 vclass_attr = '__mrv_virtual_subtype__'
657
658
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
664
665
666
667
668 clsinstance = object.__new__(nodeTypeCls)
669
670 object.__setattr__(clsinstance, '_apiobj', apiobj)
671 return clsinstance
672
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
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
683
684 object.__setattr__(dagnode, '_apidagpath', mdagpath)
685
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
699
700
701
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)
712 return newinst
713
714 return None
715
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
725 return newpath
726
727
728
729
730
731
732
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
740 """:return: True if given api object matches our specifications """
741 if self[2]:
742 setnode = NodeFromObj(apiobj)
743 for elmplug in setnode.usedBy:
744 iplug = elmplug.minput()
745 if iplug.isNull():
746 continue
747
748 if iplug.node().hasFn(api.MFn.kGeometryFilt):
749 return True
750
751
752 return False
753
754
755 if self[1]:
756 return apiobj.apiType() == self[0]
757
758
759 return apiobj.hasFn(self[0])
760
761
762
763
764
765
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):
793 raise TypeError("Can only create types being subclasses of Node, not %r" % cls)
794
795
796 typename = uncapitalize(cls.__name__)
797 instname = typename
798 if issubclass(cls, Shape):
799 instname = "%s|%sShape" % (instname, instname)
800
801
802 return createNode(instname, typename, **kwargs)
803
804
805 objorname = args[0]
806 mobject_or_mdagpath = None
807
808
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
827
828
829 return _checkedInstanceCreationDagPathSupport(mobject_or_mdagpath, cls, Node)
830
831
832
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:
840 otherapiobj = other.object()
841
842
843 return self.object() == otherapiobj
844
847
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'):
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
866
867
868
869
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
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
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
915
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):
953
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."""
961
964 """ Implements access to dependency nodes"""
965
966
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:
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
979
980
981
982
983
984
985
986 attr = str(attr)
987 setattr(type(self), attr, property(lambda self: self.findPlug(attr)))
988
989 return plug
990
992 """:return: name of this object"""
993 return self.name()
994
996 """:return: class call syntax"""
997 import traceback
998 return '%s("%s")' % (self.__class__.__name__, DependNode.__str__(self))
999
1000
1001
1002
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
1019 duplnode = NodeFromStr(cmds.duplicate(str(self))[0])
1020
1021
1022
1023
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
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
1037 self.copyTo(duplnode, *args, **kwargs)
1038 return duplnode
1039
1040
1041
1042
1043 fSetsObject = SetFilter(api.MFn.kSet, True, 0)
1044 fSets = SetFilter(api.MFn.kSet, False, 0)
1045
1046
1047
1048
1050 """:return: message plug - for non dag nodes, this will be connected """
1051 return self.message
1052
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
1065
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
1076
1077 return outlist
1078
1079
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
1103
1104
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
1120 if newname == api.MFnDependencyNode(self.object()).name():
1121 return self
1122
1123
1124 if not renameOnClash:
1125 exists = False
1126
1127 if isinstance(self, DagNode):
1128 parent = self.parent()
1129 if parent:
1130 testforobject = parent.fullChildName(newname)
1131 if objExists(testforobject):
1132 raise RuntimeError("Object %s did already exist - renameOnClash could have resolved this issue" % testforobject)
1133
1134 else:
1135 exists = objExists(newname)
1136
1137 if exists:
1138 raise RuntimeError("Node named %s did already exist, failed to rename %s" % (newname, self))
1139
1140
1141
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)
1146
1147
1148
1149
1150 shapenames = shapes = None
1151
1152
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
1164
1165
1166
1167 if shapes:
1168 for shape,shapeorigname in zip(shapes, shapenames):
1169 mod.renameNode(shape.object(), shapeorigname)
1170
1171
1172
1173 mod.doIt()
1174
1175 return self
1176
1177 @undoable
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
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
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
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
1219 """Remove the given attribute from the node
1220
1221 :param attr: see `addAttribute`"""
1222
1223 attrname = api.MFnAttribute(attr).name()
1224 try:
1225 self.findPlug(attrname, False)
1226 except RuntimeError:
1227
1228 return
1229
1230 self._addRemoveAttr(attr, False)
1231
1232 @undoable
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
1243
1244 @undoable
1259
1260
1261
1262
1264 """:return: MPlugArray of connected plugs"""
1265 cons = api.MPlugArray()
1266 mfn = DependNode._mfncls(self.object()).getConnections(cons)
1267 return cons
1268
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
1281 attrs = cmds.affects(attribute , str(self), by=by)
1282 return [self.attribute(an) for an in attrs]
1283
1284
1285
1286
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
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
1298
1299
1301 """:return: the MObject attached to this Node"""
1302 return self._apiobj
1303
1304 apiObject = object
1305
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
1313 return cmds.referenceQuery(str(self) , f=1)
1314
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
1322
1323
1324 if env.appVersion() < 2012:
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:
1333
1334 - class Entity(ContainerBase):
1336
1337
1338
1339 -class DagNode(Entity, iDagItem):
1340 """ Implements access to DAG nodes"""
1341
1342 _sep = "|"
1343 kNextPos = MFnDagNode.kNextPos
1344
1353
1356
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
1373 raise IndexError("Parent with index %i did not exist for %r" % (index, self))
1374
1375
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
1383
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
1432
1433
1434 testforobject = parentnode.fullChildName(self.basename())
1435 if objExists(testforobject):
1436 raise RuntimeError("Object %s did already exist" % testforobject)
1437
1438
1439
1440 if keepWorldSpace:
1441
1442 self._setWorldspaceTransform(parentnode)
1443
1444
1445
1446
1447 mod = None
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
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
1461
1462 mod.doIt()
1463
1464
1465
1466 if parentnode:
1467 for child in parentnode.children():
1468 if DependNode.__eq__(self, child):
1469 return child
1470 else:
1471 return NodeFromObj(self.object())
1472
1473
1474
1475 raise AssertionError("Could not find self in children after reparenting")
1476
1477 @undoable
1479 """As `reparent`, but will unparent this transform under the scene root"""
1480 return self.reparent(None, **kwargs)
1481
1482 @undoable
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
1503 if not allowZeroParents:
1504 if childNode.instanceCount(False) == 1:
1505 if isinstance(childNode, Transform):
1506 return childNode.reparent(None)
1507 else:
1508
1509
1510 raise RuntimeError("Shapenodes cannot be unparented if no parent transform would be left")
1511
1512
1513
1514 op = undo.GenericOperation()
1515 dagfn = api.MFnDagNode(self.dagPath())
1516
1517
1518
1519
1520 op.setDoitCmd(dagfn.removeChild, childNode.object())
1521 op.setUndoitCmd(self.addChild, childNode, keepExistingParent=True)
1522 op.doIt()
1523
1524 return NodeFromObj(childNode.object())
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
1551 is_direct_instance = childNode.instanceCount(0) > 1
1552 if not keepExistingParent and not is_direct_instance:
1553 return childNode.reparent(self, renameOnClash=renameOnClash, raiseOnInstance=False,
1554 keepWorldSpace = keepWorldSpace)
1555
1556
1557
1558
1559
1560
1561 children = None
1562
1563 if isinstance(childNode, Transform):
1564 children = self.childTransforms()
1565 else:
1566 children = self.shapes()
1567
1568
1569 for exChild in children:
1570 if DependNode.__eq__(childNode, exChild):
1571 return exChild
1572 del(children)
1573
1574 if not renameOnClash:
1575
1576
1577
1578 testforobject = self.fullChildName(childNode.basename())
1579 if objExists(testforobject):
1580 raise RuntimeError("Object %s did already exist below %r" % (testforobject , self))
1581
1582
1583
1584
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
1600
1601
1602
1603 undocmdCall = None
1604 parentTransform = None
1605 if not keepExistingParent:
1606
1607 parentTransform = childNode.parent()
1608 validParent = parentTransform
1609 if not validParent:
1610
1611
1612 worldobj = api.MFnDagNode(childNode.dagPath()).parent(0)
1613 validParent = DagNode(worldobj)
1614
1615
1616 parentDagFn = api.MFnDagNode(validParent.dagPath())
1617 childNodeObject = childNode.object()
1618
1619 docmd = Call(parentDagFn.removeChild, childNodeObject)
1620
1621 undocmdCall = Call(parentDagFn.addChild, childNodeObject, MFnDagNode.kNextPos, True)
1622
1623
1624 op.addCmd(docmd, undocmdCall)
1625
1626
1627 op.doIt()
1628
1629
1630
1631
1632 dagIndex = pos
1633 if pos == self.kNextPos:
1634 dagIndex = self.childCount() - 1
1635 newChildNode = NodeFromObj(MDagPathUtil.childPathAtIndex(self.dagPath(), dagIndex))
1636
1637
1638 undocmd.args = [newChildNode]
1639
1640
1641
1642
1643
1644 if not keepExistingParent and not parentTransform and undocmdCall is not None:
1645 undocmdCall.func = cmds.parent
1646 undocmdCall.args = [str(newChildNode)]
1647 undocmdCall.kwargs = { "add":1, "world":1 }
1648
1649 return newChildNode
1650
1651 @undoable
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
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)
1667 return parentnode.addChild(self, keepExistingParent = False, **kwargs)
1668
1669 @undoable
1671 """Remove ourselves from given parentnode
1672
1673 :return: None"""
1674 return parentnode.removeChild(self)
1675
1676
1677
1678
1679 @undoable
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
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
1729
1730
1731 if not newpath:
1732 if newTransform:
1733 newpath = "%s|%s" % (_getUniqueName(self.transform()), self.basename())
1734 else:
1735 newpath = _getUniqueName(self)
1736
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
1746
1747
1748 dagtokens = newpath.split('|')
1749
1750
1751
1752
1753
1754 numtokens = 3
1755 shouldbe = '|transformname|shapename'
1756 if not selfIsShape:
1757 numtokens = 2
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
1763
1764
1765
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
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782 duplicate_node_parent = NodeFromObj(api.MFnDagNode(self.dagPath()).duplicate(False, False))
1783
1784
1785
1786
1787
1788 childsourceparent = self.transform()
1789 self_shape_duplicated = None
1790
1791 srcchildren = childsourceparent.childrenDeep()
1792 destchildren = duplicate_node_parent.childrenDeep()
1793
1794
1795 if len(srcchildren) != len(destchildren):
1796
1797
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
1803
1804 selfbasename = self.basename()
1805 for i,targetchild in enumerate(destchildren):
1806 srcchildbasename = srcchildren[i].basename()
1807 targetchild.rename(srcchildbasename)
1808
1809
1810 if not self_shape_duplicated and selfbasename == srcchildbasename:
1811 self_shape_duplicated = targetchild
1812
1813
1814
1815
1816
1817
1818
1819 parenttokens = dagtokens[:-1]
1820 leafobjectname = dagtokens[-1]
1821 duplparentname = None
1822 if selfIsShape and newTransform:
1823 parenttokens = dagtokens[:-2]
1824 duplparentname = dagtokens[-2]
1825
1826
1827
1828 if parenttokens:
1829 parentnodepath = '|'.join(parenttokens)
1830 parentnode = childsourceparent
1831
1832
1833
1834
1835
1836
1837
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
1846
1847
1848
1849
1850 if parentnode is not None:
1851 if selfIsShape and not newTransform:
1852
1853
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
1859
1860 else:
1861
1862 duplicate_node_parent = duplicate_node_parent.reparent(parentnode, renameOnClash=renameOnClash)
1863
1864
1865
1866
1867
1868 if duplparentname and duplicate_node_parent is not None:
1869 duplicate_node_parent = duplicate_node_parent.rename(duplparentname, renameOnClash=renameOnClash)
1870
1871
1872
1873
1874 final_node = rename_target = duplicate_node_parent
1875
1876
1877 if selfIsShape:
1878 final_node = rename_target = self_shape_duplicated
1879
1880
1881
1882
1883
1884
1885 final_node = rename_target.rename(leafobjectname, autocreateNamespace = autocreateNamespace,
1886 renameOnClash=renameOnClash)
1887
1888
1889 self.copyTo(final_node, **kwargs)
1890 return final_node
1891
1892
1893
1894
1895
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
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
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
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
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
1942
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
1952 """:return: fully qualified (long) name of this dag node"""
1953 return self.fullPathName()
1954
1955
1956 basename = iDagItem.basename
1957
1958
1959
1960
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
1979
1981 """:return: Maya node of the parent of this instance or None if this is the root"""
1982
1983 copy = MDagPath(self.dagPath())
1984 copy.pop(1)
1985 if copy.length() == 0:
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
2007 return out
2008
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()))
2019 return [s for s in shapeNodes if predicate(s)]
2020
2025
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
2032 """:return: Node to the instance identified by instanceNumber
2033 :param instanceNumber: range(0, self.instanceCount()-1)"""
2034
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
2043 return NodeFromObj(MDagPath(allpaths[instanceNumber]))
2044
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
2059
2060
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
2072 """:return: MDagPath attached to this node from a cached location"""
2073 return self._apidagpath
2074
2076 """:return: MObject associated with the path of this instance from a cached location"""
2077 return self._apiobj
2078
2080 """:return: MObject as retrieved from the MDagPath of our Node"""
2081 self._apiobj = self._apidagpath.node()
2082 cls = type(self)
2083 object.__setattr__(self, 'object', instancemethod(cls._object_cached, self, cls))
2084 return self._apiobj
2085
2086
2087
2088 object = _object_delayed
2089
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
2099 """:return: our dag path as this is our api object - the object defining this node best"""
2100 return self.dagPath()
2101
2102
2103
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
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
2126 for i in range(allpaths.length()):
2127
2128 dagpath = allpaths[i]
2129 if dagpath.instanceNumber() != ownNumber:
2130 yield NodeFromObj(MDagPath(dagpath))
2131
2132
2133
2134
2135
2136
2137
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
2149
2150 mobject = args[0]
2151 if cls != cls._base_cls_:
2152
2153 newinst = object.__new__(cls, mobject)
2154
2155
2156
2157
2158 newinst._apiobj = newinst
2159
2160 return newinst
2161
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
2170 _new_mixin.__name__ = '__new__'
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
2193
2194
2195
2196 mfninst = cls._mfncls()
2197 attr = mfninst.create(full_name, brief_name, *args, **kwargs)
2198 return cls(attr)
2199
2200
2201 Attribute._base_cls_ = Attribute
2205
2209
2212 @classmethod
2214 mfninst = cls._mfncls()
2215 attr = getattr(mfninst, method_name)(*args)
2216 return cls(attr)
2217
2218 @classmethod
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
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
2235
2239
2243
2247
2251
2255
2256
2257
2258
2259
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
2278
2279
2280
2281 mfninst = cls._mfncls()
2282 data = mfninst.create(*args, **kwargs)
2283 return cls(data)
2284
2285 Data._base_cls_ = Data
2290
2294
2298
2302
2306
2310
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
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
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
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
2346
2350
2353 """:note: maya 2011 and newer"""
2354 pass
2355
2359
2363
2366 """Wraps geometry data providing additional convenience methods"""
2367
2369 """:return: an object id that is guaranteed to be unique
2370 :note: use it with addObjectGroup to create a new unique group"""
2371
2372 objgrpid = 0
2373 for ogid in range(self.objectGroupCount()):
2374 exog = self.objectGroup(ogid)
2375 while exog == objgrpid:
2376 objgrpid += 1
2377
2378 return objgrpid
2379
2383
2387
2391
2395
2399
2403
2407
2410 """Improves the default wrap by adding some required methods to deal with
2411 component lists"""
2412
2414 """:return: the item at the given index"""
2415 return self._mfncls(self)[index]
2416
2418 """:return: number of components stored in this data"""
2419 return self.length()
2420
2422 """:return: True if the given component is contained in this data"""
2423 return self.has(component)
2424
2428
2429
2430
2431
2432
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
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
2453
2454 cdata = cls._mfncls().create(component_type)
2455 return cls(cdata)
2456
2457 @classmethod
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
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
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
2483 """precreated class for ease-of-use"""
2484 _mfnType = api.MFn.kSingleIndexedComponent
2485
2487 """:return: MIntArray containing the indices this component represents"""
2488 u = api.MIntArray()
2489 api.MFnSingleIndexedComponent(self).getElements(u)
2490 return u
2491
2492
2493 elements = getElements
2494
2496 """Fixes some functions that would not work usually """
2497 _mfnType = api.MFn.kDoubleIndexedComponent
2498
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
2509 elements = getElements
2510
2513 """precreated class for ease-of-use"""
2514 _mfnType = api.MFn.kTripleIndexedComponent
2515
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
2527 elements = getElements
2528
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
2540
2541 @classmethod
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:
2549 return None
2550 return copy
2551
2552 @classmethod
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
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
2582
2583
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
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
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
2624
2625
2626
2627
2628
2629
2630
2631 -class Reference(DependNode):
2632 """Implements additional utilities to work with references"""
2633
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
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
2651
2652 @undoable
2661
2662
2663
2664
2665
2669
2673
2674 @undoable
2678
2679 @undoable
2683
2684 @undoable
2688
2689 @undoable
2693
2694
2695
2696
2697 -class Shape(DagNode):
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
2709 fSetsRenderable = SetFilter(api.MFn.kShadingEngine, False, 0)
2710 fSetsDeformer = SetFilter(api.MFn.kSet, True , 1)
2711
2712
2713
2714
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()
2722
2723
2724
2725 if allow_compoents:
2726 components = api.MObjectArray()
2727
2728
2729 for dplug in iogplug.moutputs():
2730 sets.append(dplug.node())
2731 components.append(MObject())
2732
2733
2734 for compplug in iogplug.mchildByName('objectGroups'):
2735 for setplug in compplug.moutputs():
2736 sets.append(setplug.node())
2737
2738
2739 compdata = compplug.mchildByName('objectGrpCompList').masData()
2740 if compdata.length() == 1:
2741 components.append(compdata[0])
2742 else:
2743 raise AssertionError("more than one compoents in list")
2744
2745
2746
2747
2748 return (sets, components)
2749 else:
2750 for dplug in iogplug.moutputs():
2751 sets.append(dplug.node())
2752 return sets
2753
2754
2755
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
2782
2783
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
2789
2790 sets = components = None
2791
2792
2793
2794
2795
2796 if not use_api or not self._apiobj.hasFn(api.MFn.kMesh) or not self.isValidMesh():
2797
2798 sets,components = self._parseSetConnections(True)
2799
2800 else:
2801
2802
2803 sets = api.MObjectArray()
2804 components = api.MObjectArray()
2805 self.getConnectedSetsAndMembers(self.instanceNumber(), sets, components, False)
2806
2807
2808
2809
2810 outlist = list()
2811 for setobj,compobj in zip(sets, components):
2812 if not setFilter(setobj):
2813 continue
2814
2815 setobj = NodeFromObj(MObject(setobj))
2816 if not compobj.isNull() and asComponent:
2817 compobj = Component(compobj)
2818 else:
2819 compobj = MObject(compobj)
2820
2821
2822 outlist.append((setobj, compobj))
2823
2824 return outlist
2825
2826
2827
2828