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

Source Code for Module mrv.maya.nt.geometry

  1  # -*- coding: utf-8 -*- 
  2  """ Contains implementations ( or improvements ) to mayas geometric shapes """ 
  3  __docformat__ = "restructuredtext" 
  4   
  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") 
 10   
 11  __all__ = ("GeometryShape", "DeformableShape", "ControlPoint", "SurfaceShape",  
 12                 "Mesh") 
13 14 -class GeometryShape( base.Shape ): # base for epydoc !
15 """Contains common methods for all geometry types""" 16 @undoable 46 # END helper method 47 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 53 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 58 if substitute: 59 target_compound_index = parent_compound.logicalIndex() 60 else: 61 target_compound_index = freeLogicalIndex(parent_compound) 62 # END get some logical index 63 64 new_parent_compound = parent_compound.array().elementByLogicalIndex( target_compound_index ) 65 66 # retrieve light link, connect other - light is only needed if we do not 67 # substitute 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) 72 # END if lightplug is connected 73 # END if no substitute required 74 75 # connect object 76 other.message.mconnectTo(new_parent_compound.child(1)) 77
78 79 # END for each output plug 80 81 82 -class DeformableShape( GeometryShape ): # base for epydoc !
83 pass 84
85 86 -class ControlPoint( DeformableShape ): # base for epydoc !
87 pass 88
89 90 -class SurfaceShape( ControlPoint ): # base for epydoc !
91 pass 92
93 94 #{ Helpers 95 96 -class _SingleIndexedComponentGenerator(object):
97 """Utility producing components, initialized with the given indices. See `Mesh` 98 for more info. """ 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 103
104 - def __init__(self, mesh, component):
105 self._mesh = mesh 106 self._component = component
107
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 113 # handle [:] slices 114 if j > self._int32b: 115 comp.setComplete(1) 116 else: 117 comp.addElements(api.MIntArray.mfromRange(i, j)) 118 # END handle slice range 119 return comp
120
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) 134 # END handle type 135 else: 136 ia = api.MIntArray.mfromList(args) 137 # END handle args 138 139 return comp.addElements(ia)
140
141 - def empty(self):
142 """:return: empty component of our type""" 143 return self._mesh.component(self._component)
144
145 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() 151
152 - def __iter__(self):
153 return iter(self._get_complete_iterator())
154
155 - def _get_complete_iterator(self):
156 return self._mesh.iterComponents(self._component)
157
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")
167
168 - def __getslice__(self, i, j):
169 self._check_component() 170 # skip full slices, as in fact no components are needed there. 171 if j > self._int32b: 172 return self._get_complete_iterator() 173 # END handle [:] slice 174 175 comp = super(_SingleIndexedComponentIterator, self).__getslice__(i,j) 176 return self._mesh.iterComponents(self._component, comp)
177
178 - def __getitem__(self, *args):
179 self._check_component() 180 comp = super(_SingleIndexedComponentIterator, self).__getitem__(*args) 181 return self._mesh.iterComponents(self._component, comp)
182 183
184 - def iterator(self):
185 """:return: Iterator for all components in the mesh""" 186 return self._get_complete_iterator()
187 188 # shortcut alias 189 iter = property(iterator)
190
191 #} END helpers 192 193 194 -class Mesh( SurfaceShape ): # base for epydoc !
195 """Implemnetation of mesh related methods to make its handling more 196 convenient 197 198 **Component Access**: 199 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 207 208 """ 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 ) ) 214 215 #{ Iterator Shortcuts
216 - def _make_component_getter(cls, component):
217 def internal(self): 218 return cls(self, component)
219 # END internal method 220 return internal 221 # END pseudo-decorator 222 223 # SETUP ITERATOR SHORTCUTS 224 for shortname, component in zip(('vtx', 'e', 'f', 'map'), eComponentType): 225 locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentIterator, component)) 226 227 # SETUP COMPONENT SHORTCUTS 228 for shortname, component in zip(('cvtx', 'ce', 'cf', 'cmap'), eComponentType): 229 locals()[shortname] = property(_make_component_getter(_SingleIndexedComponentGenerator, component)) 230 231 #} END iterator shortcuts 232 233 #{ Utilities 234
235 - def copyTweaksTo( self, other ):
236 """Copy our tweaks onto another mesh 237 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() )
243 # END for each source plug in pnts 244
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
254 255 @undoable
256 - def copyAssignmentsTo( self, other, **kwargs ):
257 """Copy set assignments including component assignments to other 258 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 )
264 265 266 @undoable
267 - def resetTweaks( self, tweak_type = eComponentType.vertex, keep_tweak_result = False ):
268 """Reset the tweaks on the given mesh shape 269 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 275 276 * [referenced] mesh *without* history: 277 copy outMesh to inMesh, resetTweaks 278 279 if referenced, plenty of reference edits are generated, ideally one operates 280 on non-referenced geomtry 281 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 } 291 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 ) 298 299 # KEEP MODE 300 ############# 301 if keep_tweak_result: 302 input_plug = self.inMesh.minput() 303 304 # history check 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") 310 # END print warning 311 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 ) 317 318 # finally reset all tweeaks 319 return self.resetTweaks( check_types, keep_tweak_result = False ) 320 else: 321 # create node of valid type 322 tweak_node = input_plug.mwrappedNode() 323 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 ) 327 328 # hook the node into the history 329 input_plug.mconnectTo(tweak_node.inputPolymesh) 330 tweak_node.output.mconnectTo(self.inMesh) 331 332 # setup uvset tweak location to tell uvset where to get tweaks from 333 if tweak_node_type_API == api.MFn.kPolyTweakUV: 334 names = list() 335 self.getUVSetNames( names ) 336 index = names.index( self.currentUVSetName( ) ) 337 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 342 343 dtweak_plug = tweak_node.findPlug(tweakattr) 344 stweak_plug = self.findPlug(attrname) 345 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() 349 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()) 359 # END exception handling 360 # END for each tweak plug 361 362 # proceed with reset of tweaks 363 pass 364 # END history handling 365 # END keep tweak result handling 366 367 arrayplug = self.findPlug(attrname) 368 dataobj = api.MFnNumericData().create( datatype ) 369 370 # reset values, do it for all components at once using a data object 371 try: 372 for p in arrayplug: 373 p.msetMObject( dataobj ) 374 except RuntimeError: 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 379 pass
380 # END for tweak type to reset 381
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 389 390 #} END utilities 391 392 #{ Iterators
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") 407 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] 413 414 return it_type(self.dagPath(), component)
415 416 #} END iterators 417 418 #( iDuplicatable
419 - def copyFrom( self, other, *args, **kwargs ):
420 """Copy tweaks and sets from other onto self 421 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 ) 426 427 setfilter = kwargs.pop( "setFilter", Mesh.fSets ) # copy all sets by default 428 if setfilter is not None: 429 other.copyAssignmentsTo( self, setFilter = setfilter )
430 431 #) END iDuplicatable 432