mrv.maya.nt.geometry
Covered: 258 lines
Missed: 15 lines
Skipped 159 lines
Percent: 94 %
  2
""" Contains implementations ( or improvements ) to mayas geometric shapes """
  3
__docformat__ = "restructuredtext"
  5
import base
  6
from mrv.enum import (create as enum, Element as elm)
  7
import maya.OpenMaya as api
  8
import logging
  9
log = logging.getLogger("mrv.maya.nt.geometry")
 11
__all__ = ("GeometryShape", "DeformableShape", "ControlPoint", "SurfaceShape", 
 12
	       "Mesh")
 14
class GeometryShape( base.Shape ):	# base for epydoc !
 15
	"""Contains common methods for all geometry types"""
 16
	@undoable
 17
	def copyLightLinks( self, other, **kwargs ):
 18
		"""Copy lightlinks from one meshShape to another
 20
		:param kwargs:
 21
			 * substitute: 
 22
			 	if True, default False, the other shape will be put
 23
				in place of self, effectively receiving it's light-links whereas self losses
 24
				them. This is practical in case you create a new shape below a transform that
 25
				had a previously visible and manipulated shape whose external connections you
 26
				wouuld like to keep"""
 27
		def freeLogicalIndex( parent_plug ):
 28
			""":return: a free parent compound index"""
 29
			ilogical = parent_plug.logicalIndex()
 30
			array_plug = parent_plug.array()
 31
			num_elments = array_plug.numElements()
 36
			for iphysical in xrange( num_elments - 1, -1, -1 ):
 37
				p_plug = array_plug[ iphysical ]
 38
				try_index = p_plug.logicalIndex() + 1
 39
				try_plug = array_plug.elementByLogicalIndex( try_index )
 41
				if try_plug.child( 0 ).minput().isNull():
 42
					return try_index
 45
			raise AssertionError( "Did not find valid free index" )
 48
		substitute = kwargs.get( "substitute", False )
 49
		for input_plug in self.message.moutputs():
 50
			node = input_plug.mwrappedNode()
 51
			if node.apiType() != api.MFn.kLightLink:
 52
				continue
 56
			parent_compound = input_plug.mparent()
 57
			target_compound_index = -1
 58
			if substitute:
 59
				target_compound_index = parent_compound.logicalIndex()
 60
			else:
 61
				target_compound_index = freeLogicalIndex(parent_compound)
 64
			new_parent_compound = parent_compound.array().elementByLogicalIndex( target_compound_index )
 68
			if not substitute:
 69
				light_plug = parent_compound.child( 0 ).minput()
 70
				if not light_plug.isNull():
 71
					light_plug.mconnectTo(new_parent_compound.child( 0 ), force=False)
 76
			other.message.mconnectTo(new_parent_compound.child(1))
 82
class DeformableShape( GeometryShape ):	# base for epydoc !
 83
	pass
 86
class ControlPoint( DeformableShape ):		# base for epydoc !
 87
	pass
 90
class SurfaceShape( ControlPoint ):	# base for epydoc !
 91
	pass
 96
class _SingleIndexedComponentGenerator(object):
 97
	"""Utility producing components, initialized with the given indices. See `Mesh`
 98
	for more info. """
 99
	__slots__ = ('_mesh', '_component')
102
	_int32b = ( 1 << 31 ) - 2
104
	def __init__(self, mesh, component):
105
		self._mesh = mesh
106
		self._component = component
108
	def __getslice__(self, i, j):
109
		comp = self._mesh.component(self._component)
114
		if j > self._int32b:
115
			comp.setComplete(1)
116
		else:
117
			comp.addElements(api.MIntArray.mfromRange(i, j))
119
		return comp
121
	def __getitem__(self, *args):
122
		comp = self._mesh.component(self._component)
123
		ia = None
124
		if len(args) == 1:
125
			arg = args[0]
126
			if hasattr(arg, 'next'):
127
				ia = api.MIntArray.mfromIter(arg)
128
			elif isinstance(arg, (list, tuple)):
129
				ia = api.MIntArray.mfromList(arg)
130
			elif isinstance(arg, api.MIntArray):
131
				ia = arg
132
			else:
133
				ia = api.MIntArray.mfromMultiple(arg)
135
		else:
136
			ia = api.MIntArray.mfromList(args)
139
		return comp.addElements(ia)
141
	def empty(self):
142
		""":return: empty component of our type"""
143
		return self._mesh.component(self._component)
146
class _SingleIndexedComponentIterator(_SingleIndexedComponentGenerator):
147
	"""Utility which produces iterators for the component type
148
	it was initialized with. As a bonus, it allows to return 
149
	quick constrained iterators using the slice and get-item notation"""
150
	__slots__ = tuple()
152
	def __iter__(self):
153
		return iter(self._get_complete_iterator())
155
	def _get_complete_iterator(self):
156
		return self._mesh.iterComponents(self._component)
158
	def _check_component(self):
159
		"""
160
		:raise NotImplementedError: if comp needs double-index component, our interface
161
			cannot support anything else than SingleIndex components"""
162
		if self._component == Mesh.eComponentType.uv:
163
			raise NotImplementedError("This Utility does not support iteration using \
164
				component-constrained iterators as it can only reproduce \
165
				SingleIndexedComponents - create the Component yourself and \
166
				use iterComponents to retrieve the iterator instead")
168
	def __getslice__(self, i, j):
169
		self._check_component()
171
		if j > self._int32b:
172
			return self._get_complete_iterator()
175
		comp = super(_SingleIndexedComponentIterator, self).__getslice__(i,j)
176
		return self._mesh.iterComponents(self._component, comp)
178
	def __getitem__(self, *args):
179
		self._check_component()
180
		comp = super(_SingleIndexedComponentIterator, self).__getitem__(*args)
181
		return self._mesh.iterComponents(self._component, comp) 
184
	def iterator(self):
185
		""":return: Iterator for all components in the mesh"""
186
		return self._get_complete_iterator()
189
	iter = property(iterator)
194
class Mesh( SurfaceShape ):		# base for epydoc !
195
	"""Implemnetation of mesh related methods to make its handling more
196
	convenient
198
	**Component Access**:
200
		>>> m.cvtx[:]                   # a complete set of components
201
		>>> m.cvtx[1:4]                 # initialized with 3 indices
202
		>>> m.cvtx[1]                   # initialized with a single index
203
		>>> m.cvtx[1,2,3]               # initialized with multiple indices
204
		>>> m.cf[(1,2,3)]               # initialized with list or tuple
205
		>>> m.ce[iter(1,2,3)]           # initialized from iterator
206
		>>> m.ce[api.MIntArray()]       # initialized from MIntArray
208
	"""
210
	eComponentType = enum( elm("vertex", api.MFn.kMeshVertComponent), 
211
							elm("edge", api.MFn.kMeshEdgeComponent ), 
212
							elm("face", api.MFn.kMeshPolygonComponent ), 
213
							elm("uv", api.MFn.kMeshMapComponent ) )
216
	def _make_component_getter(cls, component):
217
		def internal(self):
218
			return cls(self, component)
220
		return internal
224
	for shortname, component in zip(('vtx', 'e', 'f', 'map'), eComponentType):
225
		locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentIterator, component))
228
	for shortname, component in zip(('cvtx', 'ce', 'cf', 'cmap'), eComponentType):
229
		locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentGenerator, component))
235
	def copyTweaksTo( self, other ):
236
		"""Copy our tweaks onto another mesh
238
		:note: we do not check topology for maximum flexibility"""
239
		opnts = other.pnts
240
		pnts = self.pnts
241
		for splug in pnts:
242
			opnts.elementByLogicalIndex( splug.logicalIndex() ).msetMObject( splug.asMObject() )
245
	def isValidMesh( self ):
246
		"""
247
		:return: True if we are nonempty and valid - emptry meshes do not work with the mfnmesh
248
			although it should ! Have to catch that case ourselves"""
249
		try:
250
			self.numVertices()
251
			return True
252
		except RuntimeError:
253
			return False
255
	@undoable
256
	def copyAssignmentsTo( self, other, **kwargs ):
257
		"""Copy set assignments including component assignments to other
259
		:param kwargs: passed to set.addMember, additional kwargs are:
260
			 * setFilter: default is fSetsRenderable"""
261
		setFilter = kwargs.pop( "setFilter", base.Shape.fSetsRenderable )
262
		for sg, comp in self.componentAssignments( setFilter = setFilter ):
263
			sg.addMember( other, comp, **kwargs )
266
	@undoable
267
	def resetTweaks( self, tweak_type = eComponentType.vertex, keep_tweak_result = False ):
268
		"""Reset the tweaks on the given mesh shape
270
		:param tweak_type: the component type(s) whose tweaks are to be removed,
271
			valid values are 'vertex' and 'uv' members of the eComponentType enumeration. 
272
			Pass in a scalar value or a list of tweak types
273
		:param keep_tweak_result: if True, the effect of the tweak will be kept. If False,
274
			it will be removed. What actually happens depends on the context
276
			* [referenced] mesh *without* history:
277
				copy outMesh to inMesh, resetTweaks
279
				if referenced, plenty of reference edits are generated, ideally one operates
280
				on non-referenced geomtry
282
			* [referenced] mesh *with* history:
283
			 	put tweakNode into mesh history, copy tweaks onto tweak node
284
		:note: currently vertex and uv tweaks will be removed if keep is enabled, thus they must
285
			both be specified"""
286
		check_types = ( isinstance( tweak_type, ( list, tuple ) ) and tweak_type ) or [ tweak_type ]
287
		type_map = {
288
							self.eComponentType.vertex : ( "pnts", api.MFnNumericData.k3Float, "polyTweak", api.MFn.kPolyTweak, "tweak" ),
289
							self.eComponentType.uv : ( "uvpt", api.MFnNumericData.k2Float, "polyTweakUV", api.MFn.kPolyTweakUV, "uvTweak" )
290
					}
292
		mia = api.MIntArray()
293
		for reset_this_type in check_types:
294
			try:
295
				attrname, datatype, tweak_node_type, tweak_node_type_API, tweakattr = type_map[ reset_this_type ]
296
			except KeyError:
297
				raise ValueError( "Tweak type %s is not supported" % reset_this_type )
301
			if keep_tweak_result:
302
				input_plug = self.inMesh.minput()
305
				if input_plug.isNull():
308
					if not ( self.eComponentType.vertex in check_types and self.eComponentType.uv in check_types ):
309
						log.warn("Currently vertex AND uv tweaks will be removed if a mesh has no history and a reset is requested")
314
					out_mesh = self.outMesh.asMObject()
315
					self.inMesh.msetMObject( out_mesh )
316
					self.cachedInMesh.msetMObject( out_mesh )
319
					return self.resetTweaks( check_types, keep_tweak_result = False )
320
				else:
322
					tweak_node = input_plug.mwrappedNode()
325
					if not tweak_node.hasFn( tweak_node_type_API ):
326
						tweak_node = base.createNode( "polyTweak", tweak_node_type, forceNewLeaf = 1  )
329
						input_plug.mconnectTo(tweak_node.inputPolymesh)
330
						tweak_node.output.mconnectTo(self.inMesh)
333
						if tweak_node_type_API == api.MFn.kPolyTweakUV:
334
							names = list()
335
							self.getUVSetNames( names )
336
							index = names.index( self.currentUVSetName( ) )
338
							own_tweak_location_plug = self.uvSet.elementByLogicalIndex( index ).mchildByName('uvSetTweakLocation')
339
							tweak_node.uvTweak.elementByLogicalIndex( index ).mconnectTo(own_tweak_location_plug)
343
					dtweak_plug = tweak_node.findPlug(tweakattr)
344
					stweak_plug = self.findPlug(attrname)
348
					stweak_plug.evaluateNumElements()
350
					mia.clear()
351
					stweak_plug.getExistingArrayAttributeIndices(mia)
352
					for i in mia:
353
						try:
354
							tplug = stweak_plug.elementByLogicalIndex(i)
355
						except RuntimeError:
356
							continue
357
						else:
358
							dtweak_plug.elementByLogicalIndex(i).msetMObject(tplug.asMObject())
363
					pass
367
			arrayplug = self.findPlug(attrname)
368
			dataobj = api.MFnNumericData().create( datatype )
371
			try:
372
				for p in arrayplug:
373
					p.msetMObject( dataobj )
374
			except RuntimeError:
379
				pass
382
	def component(self, component_type):
383
		""":return: A component object able to hold the given component type
384
		:param component_type: a member of the `eComponentType` enumeration"""
385
		if component_type not in self.eComponentType:
386
			raise ValueError("Invalid component type")
387
		return base.SingleIndexedComponent.create(component_type.value())
393
	def iterComponents(self, component_type, component=api.MObject()):
394
		"""
395
		:return: MItIterator matching your component_type to iteartor over items
396
			on this mesh
397
		:param component_type: 
398
		 * vertex -> MItMeshVertex
399
		 * edge -> MItMeshEdge
400
		 * face -> MItMeshPolygon
401
		 * uv -> MItMeshFaceVertex
402
		:param component: if not kNullObject, the iterator returned will be constrained
403
			to the given indices as described by the Component. Use `component` to retrieve 
404
			a matching component type's instance"""
405
		if component_type not in self.eComponentType:
406
			raise ValueError("Invalid component type")
408
		ec = self.eComponentType
409
		it_type = { 	ec.vertex : api.MItMeshVertex,
410
						ec.edge   : api.MItMeshEdge, 
411
						ec.face   : api.MItMeshPolygon, 
412
						ec.uv     : api.MItMeshFaceVertex}[component_type] 
414
		return it_type(self.dagPath(), component)
419
	def copyFrom( self, other, *args, **kwargs ):
420
		"""Copy tweaks and sets from other onto self
422
		:param kwargs:
423
			 * setFilter: if given, default is fSets, you may specify the types of sets to copy
424
						if None, no set conenctions will be copied """
425
		other.copyTweaksTo( self )
427
		setfilter = kwargs.pop( "setFilter", Mesh.fSets )		# copy all sets by default
428
		if setfilter is not None:
429
			other.copyAssignmentsTo( self, setFilter = setfilter )