2
""" Contains implementations ( or improvements ) to mayas geometric shapes """
3
__docformat__ = "restructuredtext"
6
from mrv.enum import (create as enum, Element as elm)
7
import maya.OpenMaya as api
9
log = logging.getLogger("mrv.maya.nt.geometry")
11
__all__ = ("GeometryShape", "DeformableShape", "ControlPoint", "SurfaceShape",
14
class GeometryShape( base.Shape ): # base for epydoc !
15
"""Contains common methods for all geometry types"""
17
def copyLightLinks( self, other, **kwargs ):
18
"""Copy lightlinks from one meshShape to another
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()
34
# one of the logical indices must be the highest one - start searching
35
# at the end of the physical array
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():
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:
54
# we are always connected to the object portion of the compound model
55
# from there we can conclude it all
56
parent_compound = input_plug.mparent()
57
target_compound_index = -1
59
target_compound_index = parent_compound.logicalIndex()
61
target_compound_index = freeLogicalIndex(parent_compound)
62
# END get some logical index
64
new_parent_compound = parent_compound.array().elementByLogicalIndex( target_compound_index )
66
# retrieve light link, connect other - light is only needed if we do not
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)
72
# END if lightplug is connected
73
# END if no substitute required
76
other.message.mconnectTo(new_parent_compound.child(1))
79
# END for each output plug
82
class DeformableShape( GeometryShape ): # base for epydoc !
86
class ControlPoint( DeformableShape ): # base for epydoc !
90
class SurfaceShape( ControlPoint ): # base for epydoc !
96
class _SingleIndexedComponentGenerator(object):
97
"""Utility producing components, initialized with the given indices. See `Mesh`
99
__slots__ = ('_mesh', '_component')
100
# to detect slices, funny thing to remark: Maya passes 1 << 31 - 1 for some reason
101
# we want to be smaller, hence -2
102
_int32b = ( 1 << 31 ) - 2
104
def __init__(self, mesh, component):
106
self._component = component
108
def __getslice__(self, i, j):
109
comp = self._mesh.component(self._component)
110
# for some reason , python inside maya returns 31 bit ints to indicate
111
# slices, instead of sys.maxint. To be sure we handle all, we just
112
# check larger/than cases
117
comp.addElements(api.MIntArray.mfromRange(i, j))
118
# END handle slice range
121
def __getitem__(self, *args):
122
comp = self._mesh.component(self._component)
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):
133
ia = api.MIntArray.mfromMultiple(arg)
136
ia = api.MIntArray.mfromList(args)
139
return comp.addElements(ia)
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"""
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):
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()
170
# skip full slices, as in fact no components are needed there.
172
return self._get_complete_iterator()
173
# END handle [:] slice
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)
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
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
209
# component types that make up a mesh
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 ) )
215
#{ Iterator Shortcuts
216
def _make_component_getter(cls, component):
218
return cls(self, component)
219
# END internal method
221
# END pseudo-decorator
223
# SETUP ITERATOR SHORTCUTS
224
for shortname, component in zip(('vtx', 'e', 'f', 'map'), eComponentType):
225
locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentIterator, component))
227
# SETUP COMPONENT SHORTCUTS
228
for shortname, component in zip(('cvtx', 'ce', 'cf', 'cmap'), eComponentType):
229
locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentGenerator, component))
231
#} END iterator shortcuts
235
def copyTweaksTo( self, other ):
236
"""Copy our tweaks onto another mesh
238
:note: we do not check topology for maximum flexibility"""
242
opnts.elementByLogicalIndex( splug.logicalIndex() ).msetMObject( splug.asMObject() )
243
# END for each source plug in pnts
245
def isValidMesh( self ):
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"""
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 )
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
286
check_types = ( isinstance( tweak_type, ( list, tuple ) ) and tweak_type ) or [ tweak_type ]
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" )
292
mia = api.MIntArray()
293
for reset_this_type in check_types:
295
attrname, datatype, tweak_node_type, tweak_node_type_API, tweakattr = type_map[ reset_this_type ]
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():
306
# assert as we had to make the handling much more complex to allow this to work right as we copy the whole mesh here
307
# containing all tweaks , not only one type
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")
312
# take the output mesh, and stuff it into the input, then proceed
313
# with the reset. This implies that all tweaks have to be removed
314
out_mesh = self.outMesh.asMObject()
315
self.inMesh.msetMObject( out_mesh )
316
self.cachedInMesh.msetMObject( out_mesh )
318
# finally reset all tweeaks
319
return self.resetTweaks( check_types, keep_tweak_result = False )
321
# create node of valid type
322
tweak_node = input_plug.mwrappedNode()
324
# create node if there is none as direct input
325
if not tweak_node.hasFn( tweak_node_type_API ):
326
tweak_node = base.createNode( "polyTweak", tweak_node_type, forceNewLeaf = 1 )
328
# hook the node into the history
329
input_plug.mconnectTo(tweak_node.inputPolymesh)
330
tweak_node.output.mconnectTo(self.inMesh)
332
# setup uvset tweak location to tell uvset where to get tweaks from
333
if tweak_node_type_API == api.MFn.kPolyTweakUV:
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)
340
# END uv special setup
341
# END create tweak node
343
dtweak_plug = tweak_node.findPlug(tweakattr)
344
stweak_plug = self.findPlug(attrname)
346
# copy the tweak values - iterate manually as the plug tends to
347
# report incorrect values if history is present - its odd
348
stweak_plug.evaluateNumElements()
351
stweak_plug.getExistingArrayAttributeIndices(mia)
354
tplug = stweak_plug.elementByLogicalIndex(i)
358
dtweak_plug.elementByLogicalIndex(i).msetMObject(tplug.asMObject())
359
# END exception handling
360
# END for each tweak plug
362
# proceed with reset of tweaks
364
# END history handling
365
# END keep tweak result handling
367
arrayplug = self.findPlug(attrname)
368
dataobj = api.MFnNumericData().create( datatype )
370
# reset values, do it for all components at once using a data object
373
p.msetMObject( dataobj )
375
# especially uvtweak array plugs return incorrect lengths, thus we may
376
# fail once we reach the end of the iteration.
377
# uvpt appears to display a lenght equalling the number of uvpoints in the mesh
378
# possibly only for the current uvset
380
# END for tweak type to reset
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())
388
# END handle face-vertex components
393
def iterComponents(self, component_type, component=api.MObject()):
395
:return: MItIterator matching your component_type to iteartor over items
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
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 )