mrv.maya.nt.base
Covered: 1720 lines
Missed: 108 lines
Skipped 1002 lines
Percent: 94 %
   2
"""
   3
Contains some basic  classes that are required to run the nodes system
   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"
  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")
  25
from maya.OpenMaya import MFnDagNode, MDagPath, MObject, MObjectHandle
  27
from itertools import chain
  28
import sys
  30
_nodesdict = None				# will be set during maya.nt initialization
  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")
  53
_nameToApiSelList = api.MSelectionList()
  54
_mfndep = api.MFnDependencyNode()
  55
_mfndag = api.MFnDagNode()
  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
  64
_api_mdagpath_node = MDagPath.node
  65
_apitype_to_name = dict()			# [int] - > type name string
  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)
  82
_plugin_type_ids_lut = set(_plugin_type_ids)
  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")))
 107
def nodeTypeToNodeTypeCls(nodeTypeName, apiobj):
 108
	""" Convert the given  node type (str) to the respective python node type class
 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:
 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)]
 123
	if isinstance(nodeTypeCls, StandinClass):
 124
		nodeTypeCls = nodeTypeCls.createCls()
 126
	return nodeTypeCls
 129
def _makeAbsolutePath(nodename):
 133
	if nodename.count('|')  == 0:
 134
		return '|' + nodename
 135
	return nodename
 137
def isAbsolutePath(nodename):
 138
	return nodename.startswith('|')
 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
 146
def toApiobj(nodename):
 147
	""" Convert the given nodename to the respective MObject
 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()
 156
	nodename = _makeAbsolutePath(nodename)
 158
	objnamelist = [nodename]
 159
	if nodename.startswith("|") and nodename.count('|') == 1:
 160
		objnamelist.append(nodename[1:])
 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)
 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
 177
			return obj
 180
	return None
 182
def toApiobjOrDagPath(nodename):
 183
	"""Convert the given nodename to the respective MObject or MDagPath
 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()
 191
	nodename = _makeAbsolutePath(nodename)
 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:])
 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)
 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
 217
				return obj
 220
	return None
 222
def toSelectionList(nodeList, mergeWithExisting = False):
 223
	"""Convert an iterable filled with Nodes to a selection list
 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
 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
 241
			sellist.add(node)
 243
	return sellist
 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.
 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
 254
	sellist = api.MSelectionList()
 255
	for node, component in nodeCompList:
 256
		sellist.add(node.dagPath(), component, mergeWithExisting)
 258
	return sellist
 260
def toSelectionListFromNames(nodenames):
 261
	"""Convert the given iterable of nodenames to a selection list
 263
	:return: MSelectionList, use `iterSelectionList` to retrieve the objects"""
 264
	sellist = api.MSelectionList()
 265
	for name in nodenames:
 266
		sellist.add(name)
 268
	return sellist
 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))
 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)
 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)
 294
	return fromSelectionList(sellist, **kwargs)
 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
 310
@undoable
 311
def delete(*args, **kwargs):
 312
	"""Delete the given nodes
 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
 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)
 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)
 346
		dagnodes.sort(key = lambda n: len(str(n).split('|')), reverse = True)
 349
		nodes = chain(dagnodes, depnodes)
 354
	for node in nodes:
 355
		if not node.isValid():
 356
			continue
 358
		try:
 359
			node.delete()
 360
		except RuntimeError:
 361
			log.error("Deletion of %s failed" % node)
 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)
 373
def activeSelectionList():
 374
	""":return: MSelectionList of the current selection list"""
 375
	sellist = api.MSelectionList()
 376
	api.MGlobal.getActiveSelectionList(sellist)
 377
	return sellist
 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)
 389
def select(*nodesOrSelectionList , **kwargs):
 390
	"""Select the given list of wrapped nodes or selection list in maya
 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()
 403
	for item in nodesOrSelectionList:
 404
		if isinstance(item, basestring):
 405
			nodenames.append(item)
 406
		else:
 407
			other.append(item)
 411
	if len(other) == 1 and isinstance(other[0], api.MSelectionList):
 412
		other = other[0]
 414
	sellist = toSelectionList(other)
 416
	if nodenames:
 417
		sellistnames = toSelectionListFromNames(nodenames)
 418
		sellist.merge(sellistnames)
 420
	adjustment = kwargs.get("listAdjustment", api.MGlobal.kReplaceList)
 421
	api.MGlobal.selectCommand(sellist , adjustment)
 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
 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 ''")
 453
	subpaths = nodename.split('|')
 455
	parentnode = None
 456
	createdNode = None
 457
	lenSubpaths = len(subpaths)
 458
	start_index = 1
 461
	if  nodename[0] != '|':
 462
		nodename = "|" + nodename				# update with pipe
 463
		subpaths.insert(0, '')
 464
		lenSubpaths += 1
 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
 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
 487
		if do_existence_checks:
 488
			nodeapiobj = toApiobj(nodepartialname)
 489
			if nodeapiobj is not None:
 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:
 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)
 504
						continue
 506
					else:
 508
						renameOnClash = True		# allow clashes and rename
 510
				else:
 512
					parentnode = createdNode = nodeapiobj
 513
					continue
 514
			else:
 515
				do_existence_checks = False
 522
		dagtoken = '|'.join(subpaths[i : i+1])
 524
		if autocreateNamespace:
 525
			nsm.createNamespace(":".join(dagtoken.split(":")[0:-1]))	# will resolve to root namespace at least
 528
		actualtype = "transform"
 529
		if is_last_iteration:
 530
			actualtype = nodetype
 536
		if parentnode or actualtype == "transform" or (is_last_iteration and is_transform_type):
 537
			if dagmod is None:
 538
				dagmod = api.MDagModifier()
 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
 547
			dagmod.renameNode(newapiobj, dagtoken)									# rename
 549
			parentnode = createdNode = newapiobj				# update parent
 550
		else:
 551
			if dgmod is None:
 552
				dgmod = api.MDGModifier()
 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
 576
				if is_shape:
 577
					trans = dagmod.createNode("transform")
 578
					newapiobj = dagmod.createNode(actualtype, trans)
 579
				else:
 580
					newapiobj = dagmod.createNode(actualtype)
 583
			mod.renameNode(newapiobj, dagtoken)									# rename
 584
			createdNode = newapiobj
 590
		_mfndep_setobject(newapiobj)
 591
		actualname = _mfndep_name()
 592
		if actualname != dagtoken:
 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:
 600
				subpaths[i] =  actualname
 601
				nodepartialname = '|'.join(subpaths[0 : i+1])
 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()
 613
	if createdNode is None:
 614
		raise RuntimeError("Failed to create %s (%s)" % (nodename, nodetype))
 616
	return NodeFromObj(createdNode)
 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
 630
	clsinstance = _checkedInstanceCreation(mobject_or_mdagpath, _lookup_type(mobject_or_mdagpath), clsToBeCreated, basecls)
 631
	if isinstance(clsinstance, DagNode):
 632
		_setupDagNodeDelayedMethods(clsinstance, apiobj, dagpath)
 634
	return clsinstance
 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
 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)"""
 647
	nodeTypeCls = nodeTypeToNodeTypeCls(typeName, apiobj)
 655
	if clsToBeCreated is not basecls and clsToBeCreated is not nodeTypeCls:
 656
		vclass_attr = '__mrv_virtual_subtype__'
 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
 668
	clsinstance = object.__new__(nodeTypeCls)
 670
	object.__setattr__(clsinstance, '_apiobj',  apiobj)
 671
	return clsinstance
 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:
 679
		object.__setattr__(dagnode, 'dagPath', instancemethod(instcls._dagPath_delayed, dagnode, instcls))
 680
		object.__setattr__(dagnode, 'object', instancemethod(instcls._object_cached, dagnode, instcls))
 681
	else:
 684
		object.__setattr__(dagnode, '_apidagpath', mdagpath)
 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
 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"""
 702
	attrtypekeys = [a for a in nodeTypeToMfnClsMap.keys() if predicate(a)]
 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
 714
	return None
 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
 725
	return newpath
 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))
 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
 748
				if iplug.node().hasFn(api.MFn.kGeometryFilt):
 749
					return True
 752
			return False		# no deformer found
 755
		if self[1]:			# exact type
 756
			return apiobj.apiType() == self[0]
 759
		return apiobj.hasFn(self[0])
 767
_api_type_tuple = (MObject, MDagPath)
 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
 775
	def __new__ (cls, *args, **kwargs):
 776
		"""return the proper class for the given object
 778
		:param args: arg[0] is the node to be wrapped
 780
			 * string: wrap the API object with the respective name
 781
			 * MObject
 782
			 * MObjectHandle
 783
			 * MDagPath
 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"""
 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)
 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)
 802
			return createNode(instname, typename, **kwargs)
 805
		objorname = args[0]
 806
		mobject_or_mdagpath = None
 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))
 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)
 829
		return _checkedInstanceCreationDagPathSupport(mobject_or_mdagpath, cls, Node)
 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()
 843
		return self.object() == otherapiobj		# does not appear to work as expected ...
 845
	def __ne__(self, other):
 846
		return not Node.__eq__(self, other)
 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))
 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()
 863
		__hash__ = __hash_2009__
 864
		__hash__.__name__ = '__hash__'
 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")
 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__]
 883
	def apiType(self):
 884
		""":return: the MFn Type id of the wrapped object"""
 885
		return self.apiObject().apiType()
 887
	def hasFn(self, mfntype):
 888
		""":return: True if our object supports the given function set type"""
 889
		return self.apiObject().hasFn(mfntype)
 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
 902
		return _apitype_to_name[apitype]
 903
	except KeyError:
 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()
 912
		_apitype_to_name[mobject_or_mdagpath.apiType()] = typename
 914
		return typename
 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
 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.
 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
 937
		clsinstance = object.__new__(nodeTypeToNodeTypeCls(_lookup_type(mobject_or_mdagpath), apiobj))
 941
		object.__setattr__(clsinstance, '_apiobj',  apiobj)
 944
		if isinstance(clsinstance, DagNode):
 945
			_setupDagNodeDelayedMethods(clsinstance, apiobj, dagpath)
 951
		clsinstance.__init__(mobject_or_mdagpath)
 952
		return clsinstance
 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))
 963
class DependNode(Node, iDuplicatable):		# parent just for epydoc -
 964
	""" Implements access to dependency nodes"""
 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()))
 986
		attr = str(attr)
 987
		setattr(type(self), attr, property(lambda self: self.findPlug(attr)))
 989
		return plug
 991
	def __str__(self):
 992
		""":return: name of this object"""
 993
		return self.name()
 995
	def __repr__(self):
 996
		""":return: class call syntax"""
 997
		import traceback
 998
		return '%s("%s")' % (self.__class__.__name__, DependNode.__str__(self))
1004
	@notundoable
1005
	def duplicate(self, name = None, *args, **kwargs):
1006
		"""Duplicate our node and return a wrapped version to it
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 !"""
1019
		duplnode = NodeFromStr(cmds.duplicate(str(self))[0])
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)
1031
		rkwargs = dict()
1032
		rkwargs['renameOnClash'] = kwargs.pop('renameOnClash', True)
1033
		rkwargs['autocreateNamespace'] = kwargs.pop('autocreateNamespace', True)
1034
		duplnode = duplnode.rename(name, **rkwargs)
1037
		self.copyTo(duplnode, *args, **kwargs)
1038
		return duplnode
1043
	fSetsObject = SetFilter(api.MFn.kSet, True, 0)				# object fSets only
1044
	fSets = SetFilter(api.MFn.kSet, False, 0)			 		# all set types
1049
	def _getSetPlug(self):
1050
		""":return: message plug - for non dag nodes, this will be connected """
1051
		return self.message
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 """
1066
		outlist = list()
1067
		iogplug = self._getSetPlug()
1069
		for dplug in iogplug.moutputs():
1070
			setapiobj = dplug.node()
1072
			if not setFilter(setapiobj):
1073
				continue
1074
			outlist.append(NodeFromObj(MObject(setapiobj)))
1077
		return outlist
1080
	sets = connectedSets
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)
1088
	def addTo(self, setnode, component = MObject(), **kwargs):
1089
		"""Add ourselves to the given set
1091
		:note: method is undoable
1092
		:see: `sets.ObjectSet`"""
1093
		return setnode.addMember(self, component = component, **kwargs)
1095
	def removeFrom(self, setnode, component = MObject()):
1096
		"""remove ourselves to the given set
1098
		:note: method is undoable
1099
		:see: `sets.ObjectSet`"""
1100
		return setnode.removeMember(self, component = component)
1106
	@undoable
1107
	def rename(self, newname, autocreateNamespace=True, renameOnClash = True):
1108
		"""Rename this node to newname
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)
1120
		if newname == api.MFnDependencyNode(self.object()).name():
1121
			return self
1124
		if not renameOnClash:
1125
			exists = False
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)
1134
			else:
1135
				exists = objExists(newname)	# depnode: check if object exists
1137
			if exists:
1138
				raise RuntimeError("Node named %s did already exist, failed to rename %s" % (newname, self))
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
1150
		shapenames = shapes = None			# HACK: this is dagnodes only (only put here for convenience, should be in DagNode)
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)
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)
1173
		mod.doIt()
1175
		return self
1177
	@undoable
1178
	def delete(self):
1179
		"""Delete this node
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()
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
1194
		if not add:
1195
			doitfunc = mfninst.removeAttribute
1197
		doitfunc(attr)
1199
	def addAttribute(self, attr):
1200
		"""Add the given attribute to the node as local dynamic attribute
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"""
1209
		attrname = api.MFnAttribute(attr).name()
1210
		try:
1211
			return self.findPlug(attrname, False)
1212
		except RuntimeError:
1213
			pass
1215
		self._addRemoveAttr(attr, True)
1216
		return self.findPlug(api.MFnAttribute(attr).name())
1218
	def removeAttribute(self, attr):
1219
		"""Remove the given attribute from the node
1221
		:param attr: see `addAttribute`"""
1223
		attrname = api.MFnAttribute(attr).name()
1224
		try:
1225
			self.findPlug(attrname, False)
1226
		except RuntimeError:
1228
			return
1230
		self._addRemoveAttr(attr, False)
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)
1244
	@undoable
1245
	def setLocked(self, state):
1246
		"""Lock or unloack this node
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()
1253
		depfn = api.MFnDependencyNode(self.object())
1255
		op = undo.GenericOperation()
1256
		op.setDoitCmd(depfn.setLocked, state)
1257
		op.setUndoitCmd(depfn.setLocked, curstate)
1258
		op.doIt()
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
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()
1281
		attrs = cmds.affects(attribute , str(self), by=by)
1282
		return [self.attribute(an) for an in attrs]
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()
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()
1300
	def object(self):
1301
		""":return: the MObject attached to this Node"""
1302
		return self._apiobj
1304
	apiObject = object		# overridden from Node
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"""
1313
		return cmds.referenceQuery(str(self) , f=1)
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()
1324
if env.appVersion() < 2012:
1325
	class Entity(DependNode):
1326
		"""Common base for dagnodes and paritions
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
1334
	class Entity(ContainerBase):
1335
		pass
1339
class DagNode(Entity, iDagItem):	# parent just for epydoc
1340
	""" Implements access to DAG nodes"""
1342
	_sep = "|"
1343
	kNextPos = MFnDagNode.kNextPos
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()
1354
	def __ne__(self, other):
1355
		return not DagNode.__eq__(self, other)
1357
	def __getitem__(self, index):
1358
		"""
1359
		:return: if index >= 0: Node(child)  at index
1361
			 * if index < 0: Node parent at  -(index+1)(if walking up the hierarchy)
1362
			 * If index is string, use DependNodes implementation
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
1373
			raise IndexError("Parent with index %i did not exist for %r" % (index, self))
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())
1384
	def _setWorldspaceTransform(self, parentnode):
1385
		"""Set ourselve's transformation matrix to our absolute worldspace transformation,
1386
		possibly relative to the optional parentnode
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
1394
		nwm = self.wm.elementByLogicalIndex(self.instanceNumber()).masData().transformation().asMatrix()
1397
		if parentnode is not None:
1399
			parentInverseMatrix = parentnode.wim.elementByLogicalIndex(parentnode.instanceNumber()).masData().transformation().asMatrix()
1400
			nwm = nwm * parentInverseMatrix
1403
		self.set(api.MTransformationMatrix(nwm))
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
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
1421
		:return : copy of self pointing to the new dag path self
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
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)
1430
		if not renameOnClash and parentnode and self != parentnode:
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)
1440
		if keepWorldSpace:
1442
			self._setWorldspaceTransform(parentnode)
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)
1452
			mod = undo.DagModifier()
1453
			mod.reparentNode(self.object(), parentnode.object())
1454
		else:
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())
1462
		mod.doIt()
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())
1475
		raise AssertionError("Could not find self in children after reparenting")
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)
1482
	@undoable
1483
	def addInstancedChild(self, childNode, position=MFnDagNode.kNextPos):
1484
		"""Add childnode as instanced child to this node
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)
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 !
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"""
1503
		if not allowZeroParents:
1504
			if childNode.instanceCount(False) == 1:
1505
				if isinstance(childNode, Transform):
1506
					return childNode.reparent(None)
1507
				else:
1510
					raise RuntimeError("Shapenodes cannot be unparented if no parent transform would be left")
1514
		op = undo.GenericOperation()
1515
		dagfn = api.MFnDagNode(self.dagPath())
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()
1524
		return NodeFromObj(childNode.object())	# will attach A new dag path respectively - it will just pick the first one it gets
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 !
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"""
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)
1561
		children = None
1563
		if isinstance(childNode, Transform):
1564
			children = self.childTransforms()
1565
		else:
1566
			children = self.shapes()
1569
		for exChild in children:
1570
			if DependNode.__eq__(childNode, exChild):
1571
				return exChild								# exchild has proper dagpath
1572
		del(children)			# release memory
1574
		if not renameOnClash:
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))
1586
		op = undo.GenericOperationStack()
1588
		pos = position
1589
		if pos != self.kNextPos:
1590
			pos = pythonIndex(pos, self.childCount())
1592
		dagfn = api.MFnDagNode(self.dagPath())
1593
		docmd = Call(dagfn.addChild, childNode.object(), pos, True)
1594
		undocmd = Call(self.removeChild, childNode)
1596
		op.addCmd(docmd, undocmd)
1603
		undocmdCall = None
1604
		parentTransform = None
1605
		if not keepExistingParent:
1607
			parentTransform = childNode.parent()
1608
			validParent = parentTransform
1609
			if not validParent:
1612
				worldobj = api.MFnDagNode(childNode.dagPath()).parent(0)
1613
				validParent = DagNode(worldobj)
1616
			parentDagFn = api.MFnDagNode(validParent.dagPath())
1617
			childNodeObject = childNode.object()
1619
			docmd = Call(parentDagFn.removeChild, childNodeObject)
1621
			undocmdCall = Call(parentDagFn.addChild, childNodeObject, MFnDagNode.kNextPos, True)	# call ourselves
1624
			op.addCmd(docmd, undocmdCall)
1627
		op.doIt()
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))
1638
		undocmd.args = [newChildNode]
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 }
1649
		return newChildNode
1651
	@undoable
1652
	def addParent(self, parentnode, **kwargs):
1653
		"""Adds ourselves as instance to the given parentnode at position
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)
1660
	@undoable
1661
	def setParent(self, parentnode, **kwargs):
1662
		"""Change the parent of self to parentnode being placed at position
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)
1669
	@undoable
1670
	def removeParent(self, parentnode ):
1671
		"""Remove ourselves from given parentnode
1673
		:return: None"""
1674
		return parentnode.removeChild(self)
1679
	@undoable
1680
	def delete(self):
1681
		"""Delete this node - this special version must be
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()
1693
	@notundoable
1694
	def duplicate(self, newpath='', autocreateNamespace=True, renameOnClash=True,
1695
				   newTransform = False, **kwargs):
1696
		"""Duplciate the given node to newpath
1698
		:param newpath: result depends on its format:
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
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)
1731
		if not newpath:		# "" or None
1732
			if newTransform:
1733
				newpath = "%s|%s" % (_getUniqueName(self.transform()), self.basename())
1734
			else:
1735
				newpath = _getUniqueName(self)
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)
1748
		dagtokens = newpath.split('|')
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'
1760
		if '|' in newpath and (newpath == '|' or len(dagtokens) < numtokens):
1761
			raise NameError("Duplicate paths should be at least %s, was %s" % (shouldbe, newpath))
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
1782
		duplicate_node_parent = NodeFromObj(api.MFnDagNode(self.dagPath()).duplicate(False, False))		# get the duplicate
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)
1791
		srcchildren = childsourceparent.childrenDeep()
1792
		destchildren = duplicate_node_parent.childrenDeep()
1795
		if len(srcchildren) != len(destchildren):
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:
1804
			selfbasename = self.basename()
1805
			for i,targetchild in enumerate(destchildren):
1806
				srcchildbasename = srcchildren[i].basename()
1807
				targetchild.rename(srcchildbasename)
1810
				if not self_shape_duplicated and selfbasename == srcchildbasename:
1811
					self_shape_duplicated = targetchild
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]
1828
		if parenttokens:			# could be [''] too if newpath = '|newpath'
1829
			parentnodepath = '|'.join(parenttokens)
1830
			parentnode = childsourceparent			# in case we have a relative name
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)
1850
			if parentnode is not None:
1851
				if selfIsShape and not newTransform:
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
1860
				else:
1862
					duplicate_node_parent = duplicate_node_parent.reparent(parentnode, renameOnClash=renameOnClash)
1868
		if duplparentname and duplicate_node_parent is not None:
1869
			duplicate_node_parent = duplicate_node_parent.rename(duplparentname, renameOnClash=renameOnClash)
1874
		final_node = rename_target = duplicate_node_parent		# item that is to be renamed to the final name later
1877
		if selfIsShape:	# want shape, have transform
1878
			final_node = rename_target = self_shape_duplicated
1885
		final_node = rename_target.rename(leafobjectname, autocreateNamespace = autocreateNamespace,
1886
										  	renameOnClash=renameOnClash)
1889
		self.copyTo(final_node, **kwargs)
1890
		return final_node
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
1903
		for parent in self.iterParents():
1904
			if getattr(parent, plugName).asInt() == cmpval:
1905
				return cmpval
1907
		return 1 - cmpval
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()
1916
		for parent in self.iterParents():
1917
			if parent.do.mchildByName('ove').asInt():
1918
				return parent.do.mchildByName(plugName).asInt()
1920
		return None
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)
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)
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)
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)
1951
	def name(self):
1952
		""":return: fully qualified (long) name of this dag node"""
1953
		return self.fullPathName()
1956
	basename = iDagItem.basename
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()
1969
		return NodeFromObj(api.MFnDagNode(self.dagPath()).parent(uint))
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"""
1976
		if isinstance(self, Transform):
1977
			return self
1978
		return NodeFromObj(self.dagPath().transform())
1980
	def parent(self):
1981
		""":return: Maya node of the parent of this instance or None if this is the root"""
1983
		copy = MDagPath(self.dagPath())
1984
		copy.pop(1)
1985
		if copy.length() == 0:		# ignore world !
1986
			return None
1987
		return NodeFromObj(copy)
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))
1999
			if asNode:
2000
				copy = NodeFromObj(copy)
2002
			if not predicate(copy):
2003
				continue
2005
			out.append(copy)
2007
		return out
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)]
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)]
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)]
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()
2031
	def instance(self, instanceNumber):
2032
		""":return: Node to the instance identified by instanceNumber
2033
		:param instanceNumber: range(0, self.instanceCount()-1)"""
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
2040
		allpaths = api.MDagPathArray()
2041
		self.getAllPaths(allpaths)
2043
		return NodeFromObj(MDagPath(allpaths[instanceNumber]))
2045
	def hasChild(self, node):
2046
		""":return: True if node is a child of self"""
2047
		return api.MFnDagNode(self.dagPath()).hasChild(node.object())
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)
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
2071
	def _dagPath_cached(self):
2072
		""":return: MDagPath attached to this node from a cached location"""
2073
		return self._apidagpath
2075
	def _object_cached(self):
2076
		""":return: MObject associated with the path of this instance from a cached location"""
2077
		return self._apiobj
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
2088
	object = _object_delayed
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
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()
2104
	def iterInstances(self, excludeSelf = False):
2105
		"""Get iterator over all (direct and indirect)instances of this node
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 """
2113
		if self.instanceCount(True) == 1:
2114
			if not excludeSelf:
2115
				yield self
2116
			raise StopIteration
2118
		ownNumber = -1
2119
		if excludeSelf:
2120
			ownNumber = self.instanceNumber()
2122
		allpaths = api.MDagPathArray()
2123
		self.getAllPaths(allpaths)
2126
		for i in range(allpaths.length()):
2128
			dagpath = allpaths[i]
2129
			if dagpath.instanceNumber() != ownNumber:
2130
				yield NodeFromObj(MDagPath(dagpath))
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
2143
	return an attribute class of the respective type for given MObject
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."""
2150
	mobject = args[0]
2151
	if cls != cls._base_cls_:
2153
		newinst = object.__new__(cls, mobject)
2158
		newinst._apiobj = newinst		########
2160
		return newinst
2162
	newinst = _createInstByPredicate(mobject, cls, cls, lambda x: x.endswith(cls._mfn_suffix_))
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()))
2167
	return newinst
2170
_new_mixin.__name__ = '__new__'
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 """
2177
	__metaclass__ = MetaClassCreatorNodes
2178
	_base_cls_ = None
2179
	_mfn_suffix_ = 'Attribute'
2181
	__new__ = _new_mixin
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")
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
2201
Attribute._base_cls_ = Attribute
2203
class UnitAttribute(Attribute):
2204
	pass
2207
class TypedAttribute(Attribute):
2208
	pass
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)
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)
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)
2233
class MessageAttribute(Attribute):
2234
	pass 
2237
class MatrixAttribute(Attribute):
2238
	pass
2241
class LightDataAttribute(Attribute):
2242
	pass 
2245
class GenericAttribute(Attribute):
2246
	pass 
2249
class EnumAttribute(Attribute):
2250
	pass 
2253
class CompoundAttribute(Attribute):
2254
	pass 
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 """
2265
	__metaclass__ = MetaClassCreatorNodes
2266
	_base_cls_ = None
2267
	_mfn_suffix_ = 'Data'
2269
	__new__ = _new_mixin
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")
2281
		mfninst = cls._mfncls() 
2282
		data = mfninst.create(*args, **kwargs)
2283
		return cls(data)
2285
Data._base_cls_ = Data
2288
class VectorArrayData(Data):
2289
	pass
2292
class UInt64ArrayData(Data):
2293
	pass
2296
class StringData(Data):
2297
	pass
2300
class StringArrayData(Data):
2301
	pass
2304
class SphereData(Data):
2305
	pass
2308
class PointArrayData(Data):
2309
	pass
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"""
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
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:
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)
2344
class NumericData(Data):
2345
	pass
2348
class NObjectData(Data):
2349
	pass
2352
class NIdData(Data):
2353
	""":note: maya 2011 and newer"""
2354
	pass
2357
class MatrixData(Data):
2358
	pass
2361
class IntArrayData(Data):
2362
	pass
2365
class GeometryData(Data):
2366
	"""Wraps geometry data providing additional convenience methods"""
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"""
2372
		objgrpid = 0
2373
		for ogid in range(self.objectGroupCount()):
2374
			exog = self.objectGroup(ogid)
2375
			while exog == objgrpid:
2376
				objgrpid += 1
2378
		return objgrpid
2381
class SubdData(GeometryData):
2382
	pass
2385
class NurbsSurfaceData(GeometryData):
2386
	pass
2389
class NurbsCurveData(GeometryData):
2390
	pass
2393
class MeshData(GeometryData):
2394
	pass
2397
class LatticeData(GeometryData):
2398
	pass
2401
class DynSweptGeometryData(Data):
2402
	pass
2405
class DoubleArrayData(Data):
2406
	pass
2409
class ComponentListData(Data):
2410
	"""Improves the default wrap by adding some required methods to deal with
2411
	component lists"""
2413
	def __getitem__(self, index):
2414
		""":return: the item at the given index"""
2415
		return self._mfncls(self)[index]
2417
	def __len__(self):
2418
		""":return: number of components stored in this data"""
2419
		return self.length()
2421
	def __contains__(self, component):
2422
		""":return: True if the given component is contained in this data"""
2423
		return self.has(component)
2426
class ArrayAttrsData(Data):
2427
	pass
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"
2442
	__new__ = _new_mixin
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")
2454
		cdata = cls._mfncls().create(component_type)
2455
		return cls(cdata)
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
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
2467
		:return: self"""
2468
		self._mfncls(self).addElements(*args)
2469
		return self
2471
	def addElement(self, *args):
2472
		"""see `addElements`
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
2480
Component._base_cls_ = Component
2482
class SingleIndexedComponent(Component):
2483
	"""precreated class for ease-of-use"""
2484
	_mfnType = api.MFn.kSingleIndexedComponent
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
2493
	elements = getElements
2495
class DoubleIndexedComponent(Component):	# derived just for epydoc
2496
	"""Fixes some functions that would not work usually """
2497
	_mfnType = api.MFn.kDoubleIndexedComponent
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)
2509
	elements = getElements
2512
class TripleIndexedComponent(Component):
2513
	"""precreated class for ease-of-use"""
2514
	_mfnType = api.MFn.kTripleIndexedComponent
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)
2527
	elements = getElements
2533
class MDagPathUtil(object):
2534
	"""Performs operations on MDagPaths which are hard or inconvenient to do otherwise
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."""
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
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)
2559
		path.numberOfShapesDirectlyBelow(uintptr)
2561
		return sutil.uint(uintptr)
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
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
2584
	@classmethod
2585
	def pop(cls, path, num):
2586
		"""Pop the given number of items off the end of the path
2588
		:return: path itself"""
2589
		path.pop(num)
2590
		return path
2592
	@classmethod
2593
	def extendToChild(cls, path, num):
2594
		"""Extend path to the given child number - can be shape or transform
2596
		:return: path itself"""
2597
		path.extendToShapeDirectlyBelow(num)
2598
		return self
2600
	@classmethod
2601
	def childPathsByFn(cls, path, fn, predicate = lambda x: True):
2602
		"""Get all children below path supporting the given MFn.type
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)]
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]
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)
2631
class Reference(DependNode):
2632
	"""Implements additional utilities to work with references"""
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)
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
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 """
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()
2666
	def getScale(self):
2667
		""":return: MVector containing the scale of the transform"""
2668
		return in_double3_out_vector(self._api_getScale)
2670
	def getShear(self):
2671
		""":return: MVector containing the shear of the transform"""
2672
		return in_double3_out_vector(self._api_getShear)
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)
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)
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)
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)
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
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"""
2709
	fSetsRenderable = SetFilter(api.MFn.kShadingEngine, False, 0)	# shading engines only
2710
	fSetsDeformer = SetFilter(api.MFn.kSet, True , 1)				# deformer sets only
2715
	def _parseSetConnections(self, allow_compoents):
2716
		"""Manually parses the set connections from self
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
2725
		if allow_compoents:
2726
			components = api.MObjectArray()
2729
			for dplug in iogplug.moutputs():
2730
				sets.append(dplug.node())
2731
				components.append(MObject())
2734
			for compplug in iogplug.mchildByName('objectGroups'):
2735
				for setplug in compplug.moutputs():
2736
					sets.append(setplug.node())		# connected set
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")
2748
			return (sets, components)
2749
		else:
2750
			for dplug in iogplug.moutputs():
2751
				sets.append(dplug.node())
2752
			return sets
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.
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"""
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]
2790
		sets = components = None
2796
		if not use_api or not self._apiobj.hasFn(api.MFn.kMesh) or not self.isValidMesh():
2798
			sets,components = self._parseSetConnections(True)
2800
		else:
2803
			sets = api.MObjectArray()
2804
			components = api.MObjectArray()
2805
			self.getConnectedSetsAndMembers(self.instanceNumber(), sets, components, False)
2810
		outlist = list()
2811
		for setobj,compobj in zip(sets, components):
2812
			if not setFilter(setobj):
2813
				continue
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
2822
			outlist.append((setobj, compobj))
2824
		return outlist