scripted/splitUVCmd.py

scripted/splitUVCmd.py
1 #-
2 # ==========================================================================
3 # Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All
4 # rights reserved.
5 #
6 # The coded instructions, statements, computer programs, and/or related
7 # material (collectively the "Data") in these files contain unpublished
8 # information proprietary to Autodesk, Inc. ("Autodesk") and/or its
9 # licensors, which is protected by U.S. and Canadian federal copyright
10 # law and by international treaties.
11 #
12 # The Data is provided for use exclusively by You. You have the right
13 # to use, modify, and incorporate this Data into other products for
14 # purposes authorized by the Autodesk software license agreement,
15 # without fee.
16 #
17 # The copyright notices in the Software and this entire statement,
18 # including the above license grant, this restriction and the
19 # following disclaimer, must be included in all copies of the
20 # Software, in whole or in part, and all derivative works of
21 # the Software, unless such copies or derivative works are solely
22 # in the form of machine-executable object code generated by a
23 # source language processor.
24 #
25 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
26 # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
27 # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
28 # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
29 # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
30 # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
31 # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
32 # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
33 # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
34 # OR PROBABILITY OF SUCH DAMAGES.
35 #
36 # ==========================================================================
37 #+
38 
39 ########################################################################
40 # DESCRIPTION:
41 #
42 # Produces the Python command "spSplitUV".
43 #
44 # The splitUV command unshares or "splits" the selected UVs of a polygonal mesh.
45 # It is also a good example of how to write poly operation nodes that properly
46 # deal with history, tweaks, and so on.
47 #
48 # For a thorough explanation of creating this command, refer to the "splitUVCmd example"
49 # topic in the Polygon API section of the online help. Note that the example in the
50 # online help uses a MEL script for demonstration. Refer to this splitUVCmd.py script
51 # for a Python version of the example.
52 #
53 # How it works:
54 #
55 # This command is based on the polyModifierCmd. It relies on the polyModifierCmd
56 # to manage "how" the effects of the splitUV operation are applied (directly
57 # on the mesh or through a modifier node). See polyModifier.py for more details.
58 #
59 # To understand the algorithm behind the splitUV operation, refer to splitUVFty.
60 #
61 # Limitations:
62 #
63 # It can only operate on a single mesh at a given time. If there is more than one
64 # mesh with selected UVs, it operates only on the first mesh found in the selection list.
65 #
66 ########################################################################
67 
68 import maya.OpenMaya as OpenMaya
69 import maya.OpenMayaMPx as OpenMayaMPx
70 import sys
71 
72 import polyModifier
73 
74 def statusError(message):
75  fullMsg = "Status failed: %s\n" % message
76  sys.stderr.write(fullMsg)
78  raise # called from exception handlers only, reraise exception
79 
80 
81 kPluginCmdName = "spSplitUV"
82 kPluginNodeTypeName = "spSplitUVNode"
83 kPluginNodeId = OpenMaya.MTypeId(0x87013)
84 
85 class splitUV(polyModifier.polyModifierCmd):
86  def __init__(self):
87  polyModifier.polyModifierCmd.__init__(self)
88  # Selected UVs
89  #
90  # Note: The MObject, fComponentList, is only ever accessed on a single call to the plugin.
91  # It is never accessed between calls and is stored on the class for access in the
92  # overriden initModifierNode() method.
93  #
94  self.__fComponentList = OpenMaya.MObject()
95  self.__fSelUVs = OpenMaya.MIntArray()
96  self.__fSplitUVFactory = splitUVFty()
97 
98 
99  def isUndoable(self):
100  return True
101 
102 
103  def doIt(self, args):
104  """
105  implements the scripted splitUV command.
106 
107  Arguments:
108  args - the argument list that was passes to the command from MEL
109  """
110  # Parse the selection list for objects with selected UV components.
111  # To simplify things, we only take the first object that we find with
112  # selected UVs and operate on that object alone.
113  #
114  # All other objects are ignored and return warning messages indicating
115  # this limitation.
116  #
117  selList = OpenMaya.MSelectionList()
119  selListIter = OpenMaya.MItSelectionList(selList)
120 
121  # The splitUV node only accepts a component list input, so we build
122  # a component list using MFnComponentListData.
123  #
124  # MIntArrays could also be passed into the node to represent the uvIds,
125  # but are less storage efficient than component lists, since consecutive
126  # components are bundled into a single entry in component lists.
127  #
128  compListFn = OpenMaya.MFnComponentListData()
129  compListFn.create()
130  found = False
131  foundMultiple = False
132 
133  while not selListIter.isDone():
134  dagPath = OpenMaya.MDagPath()
135  component = OpenMaya.MObject()
136  itemMatches = True
137  selListIter.getDagPath(dagPath, component)
138 
139  # Check for selected UV components
140  #
141  if itemMatches and (component.apiType() == OpenMaya.MFn.kMeshMapComponent):
142  if not found:
143  # The variable 'component' holds all selected components on the selected
144  # object, thus only a single call to MFnComponentListData::add() is needed
145  # to store the selected components for a given object.
146  #
147  compListFn.add(component)
148 
149  # Copy the component list created by MFnComponentListData into our local
150  # component list MObject member.
151  #
152  self.__fComponentList = compListFn.object()
153 
154  # Locally store the actual uvIds of the selected UVs so that this command
155  # can directly modify the mesh in the case when there is no history and
156  # history is turned off.
157  #
158  compFn = OpenMaya.MFnSingleIndexedComponent(component)
159  compFn.getElements(self.__fSelUVs)
160 
161  # Ensure that this DAG path will point to the shape of our object.
162  # Set the DAG path for the polyModifierCmd.
163  #
164  dagPath.extendToShape()
165  self._setMeshNode(dagPath)
166  found = True
167  else:
168  # Break once we have found a multiple object holding selected UVs, since
169  # we are not interested in how many multiple objects there are, only
170  # the fact that there are multiple objects.
171  #
172  foundMultiple = True
173  break
174 
175  selListIter.next()
176 
177  if foundMultiple:
178  self.displayWarning("Found more than one object with selected UVs - Only operating on first found object.")
179 
180  # Initialize the polyModifierCmd node type - mesh node already set
181  #
182  self._setModifierNodeType(kPluginNodeId)
183 
184  if found:
185  if self.__validateUVs():
186  # Now, pass control over to the polyModifierCmd._doModifyPoly() method
187  # to handle the operation.
188  #
189  try:
190  self._doModifyPoly()
191  except:
192  self.displayError("splitUV command failed!")
193  raise
194  else:
195  self.setResult("splitUV command succeeded!")
196  else:
197  self.displayError("splitUV command failed: Selected UVs are not splittable")
198  else:
199  self.displayError("splitUV command failed: Unable to find selected UVs")
200 
201 
202  def redoIt(self):
203  """
204  Implements redo for the scripted splitUV command.
205 
206  This method is called when the user has undone a command of this type
207  and then redoes it. No arguments are passed in as all of the necessary
208  information is cached by the doIt method.
209  """
210  try:
211  self._redoModifyPoly()
212  self.setResult("splitUV command succeeded!")
213  except:
214  self.displayError("splitUV command failed!")
215  raise
216 
217 
218  def undoIt(self):
219  """
220  implements undo for the scripted splitUV command.
221 
222  This method is called to undo a previous command of this type. The
223  system should be returned to the exact state that it was it previous
224  to this command being executed. That includes the selection state.
225  """
226  try:
227  self._undoModifyPoly()
228  self.setResult("splitUV undo succeeded!")
229  except:
230  self.displayError("splitUV undo failed!")
231  raise
232 
233 
234  def _initModifierNode(self, modifierNode):
235  # We need to tell the splitUV node which UVs to operate on. By overriding
236  # the polyModifierCmd._initModifierNode() method, we can insert our own
237  # modifierNode initialization code.
238  #
239  depNodeFn = OpenMaya.MFnDependencyNode(modifierNode)
240  uvListAttr = depNodeFn.attribute("inputComponents")
241 
242  # Pass the component list down to the splitUV node
243  #
244  uvListPlug = OpenMaya.MPlug(modifierNode, uvListAttr)
245  uvListPlug.setMObject(self.__fComponentList)
246 
247 
248  def _directModifier(self, mesh):
249  self.__fSplitUVFactory.setMesh(mesh)
250  self.__fSplitUVFactory.setUVIds(self.__fSelUVs)
251 
252  # Now, perform the splitUV
253  #
254  self.__fSplitUVFactory.doIt()
255 
256 
257  def __validateUVs(self):
258  """
259  Validate the UVs for the splitUV operation. UVs are valid only if they are shared
260  by more than one face. While the splitUVNode is smart enough to not process the
261  split if a UV is not splittable, a splitUV node is still created by the polyModifierCmd.
262  So call this method to validate the UVs before calling _doModifyPoly().
263 
264  validateUVs() will return true so long as there is at least one valid UV. It will
265  also prune out any invalid UVs from both the component list and UVId array.
266  """
267  # Get the mesh that we are operating on
268  #
269  dagPath = self._getMeshNode()
270  mesh = dagPath.node()
271 
272  # Get the number of faces sharing the selected UVs
273  #
274  meshFn = OpenMaya.MFnMesh(mesh)
275  polyIter = OpenMaya.MItMeshPolygon(mesh)
276  selUVFaceCountArray = OpenMaya.MIntArray()
277 
278  indexParam = OpenMaya.MScriptUtil(0)
279  indexPtr = indexParam.asIntPtr()
280 
281  count = 0
282  selUVsCount = self.__fSelUVs.length()
283  for i in range(selUVsCount):
284  while not polyIter.isDone():
285  if polyIter.hasUVs():
286  polyVertCount = polyIter.polygonVertexCount()
287 
288  for j in range(polyVertCount):
289  polyIter.getUVIndex(j, indexPtr)
290  UVIndex = indexParam.getInt(indexPtr)
291 
292  if UVIndex == self.__fSelUVs[i]:
293  count += 1
294  break
295  polyIter.next()
296  selUVFaceCountArray.append(count)
297 
298  # Now, check to make sure that at least one UV is being shared by more than one
299  # face. So long as we have one UV that we can operate on, we should proceed and let
300  # the splitUVNode ignore the UVs which are only shared by one face.
301  #
302  isValid = False
303  validUVIndices = OpenMaya.MIntArray()
304 
305  for i in range(selUVsCount):
306  if selUVFaceCountArray[i] > 1:
307  isValid = True
308  validUVIndices.append(i)
309 
310  if isValid:
311  self.__pruneUVs(validUVIndices)
312 
313  return isValid
314 
315 
316  def __pruneUVs(self, validUVIndices):
317  """
318  This method will remove any invalid UVIds from the component list and UVId array.
319  The benefit of this is to reduce the amount of extra processing that the node would
320  have to perform. It will result in less iterations through the mesh as there are
321  less UVs to search for.
322  """
323  validUVIds = OpenMaya.MIntArray()
324 
325  for i in range(validUVIndices.length()):
326  uvIndex = validUVIndices[i]
327  validUVIds.append(self.__fSelUVs[uvIndex])
328 
329  # Replace the local int array of UVIds
330  #
331  self.__fSelUVs.clear()
332  self.__fSelUVs = validUVIds
333 
334  # Build the list of valid components
335  #
337  try:
338  compFn.create(OpenMaya.MFn.kMeshMapComponent)
339  except:
340  statusError("compFn.create( MFn::kMeshMapComponent )")
341 
342  try:
343  compFn.addElements(validUVIds)
344  except:
345  statusError("compFn.addElements( validUVIds )")
346 
347  # Replace the component list
348  #
349  component = compFn.object()
350  compListFn = OpenMaya.MFnComponentListData()
351  compListFn.create()
352  try:
353  compListFn.add(component)
354  except:
355  statusError("compListFn.add( component )")
356 
357  self.__fComponentList = compListFn.object()
358 
359 
360 #####################################################################
361 ## FACTORY ##########################################################
362 #####################################################################
363 
364 # Overview:
365 #
366 # The splitUV factory implements the actual splitUV operation. It takes in
367 # only two parameters:
368 #
369 # 1) A polygonal mesh
370 # 2) An array of selected UV Ids
371 #
372 # The algorithm works as follows:
373 #
374 # 1) Parse the mesh for the selected UVs and collect:
375 #
376 # (a) Number of faces sharing each UV
377 # (stored as two arrays: face array, indexing/offset array)
378 # (b) Associated vertex Id
379 #
380 # 2) Create (N-1) new UVIds for each selected UV, where N represents the number of faces
381 # sharing the UV.
382 #
383 # 3) Set each of the new UVs to the same 2D location on the UVmap.
384 #
385 # 3) Arbitrarily let the last face in the list of faces sharing this UV to keep the original
386 # UV.
387 #
388 # 4) Assign each other face one of the new UVIds.
389 #
390 #
391 class splitUVFty(polyModifier.polyModifierFty):
392  def __init__(self):
393  polyModifier.polyModifierFty.__init__(self)
394  # Mesh Node
395  # Note: We only make use of this MObject during a single call of
396  # the splitUV plugin. It is never maintained and used between
397  # calls to the plugin as the MObject handle could be invalidated
398  # between calls to the plugin.
399  #
400  self.__fMesh = OpenMaya.MObject()
401  self.__fSelUVs = OpenMaya.MIntArray()
402  self.__fSelUVs.clear()
403 
404 
405  def setMesh(self, mesh):
406  self.__fMesh = mesh
407 
408 
409  def setUVIds(self, uvIds):
410  self.__fSelUVs = uvIds
411 
412 
413  def doIt(self):
414  """
415  Performs the actual splitUV operation on the given object and UVs
416  """
417  ####################################
418  # Declare our processing variables #
419  ####################################
420 
421  # Face Id and Face Offset map to the selected UVs
422  #
423  selUVFaceIdMap = OpenMaya.MIntArray()
424  selUVFaceOffsetMap = OpenMaya.MIntArray()
425 
426  # Local Vertex Index map to the selected UVs
427  #
428  selUVLocalVertIdMap = OpenMaya.MIntArray()
429 
430  #################################################
431  # Collect necessary information for the splitUV #
432  # #
433  # - uvSet #
434  # - faceIds / localVertIds per selected UV #
435  #################################################
436 
437  meshFn = OpenMaya.MFnMesh(self.__fMesh)
438  selUVSet = meshFn.currentUVSetName()
439 
440  indexParam = OpenMaya.MScriptUtil(0)
441  indexPtr = indexParam.asIntPtr()
442 
443  offset = 0
444  selUVsCount = self.__fSelUVs.length()
445  polyIter = OpenMaya.MItMeshPolygon(self.__fMesh)
446  for i in range(selUVsCount):
447  selUVFaceOffsetMap.append(offset)
448 
449  polyIter.reset()
450  while not polyIter.isDone():
451  if polyIter.hasUVs():
452  polyVertCount = polyIter.polygonVertexCount()
453 
454  for j in range(polyVertCount):
455  polyIter.getUVIndex(j, indexPtr)
456  UVIndex = indexParam.getInt(indexPtr)
457 
458  if UVIndex == self.__fSelUVs[i]:
459  selUVFaceIdMap.append(polyIter.index())
460  selUVLocalVertIdMap.append(j)
461  offset += 1
462  break
463 
464  polyIter.next()
465 
466  # Store total length of the faceId map in the last element of
467  # the offset map so that there is a way to get the number of faces
468  # sharing each of the selected UVs
469  #
470  selUVFaceOffsetMap.append(offset)
471 
472  ###############################
473  # Begin the splitUV operation #
474  ###############################
475 
476  currentUVCount = meshFn.numUVs(selUVSet)
477 
478  for i in range(selUVsCount):
479  # Get the current FaceId map offset
480  #
481  offset = selUVFaceOffsetMap[i]
482 
483  # Get the U and V values of the current UV
484  #
485  uvId = self.__fSelUVs[i]
486 
487  uParam = OpenMaya.MScriptUtil(0.0)
488  uPtr = uParam.asFloatPtr()
489  vParam = OpenMaya.MScriptUtil(0.0)
490  vPtr = vParam.asFloatPtr()
491  meshFn.getUV(uvId, uPtr, vPtr, selUVSet)
492  u = uParam.getFloat(uPtr)
493  v = vParam.getFloat(vPtr)
494 
495  # Get the number of faces sharing the current UV
496  #
497  faceCount = selUVFaceOffsetMap[i + 1] - selUVFaceOffsetMap[i]
498 
499  # Arbitrarily choose that the last faceId in the list of faces
500  # sharing this UV, will keep the original UV.
501  #
502  for j in range(faceCount-1):
503  meshFn.setUV(currentUVCount, u, v, selUVSet)
504 
505  localVertId = selUVLocalVertIdMap[offset]
506  faceId = selUVFaceIdMap[offset]
507 
508  meshFn.assignUV(faceId, localVertId, currentUVCount, selUVSet)
509 
510  currentUVCount += 1
511  offset += 1
512 
513 
514 #####################################################################
515 ## NODE #############################################################
516 #####################################################################
517 
518 class splitUVNode(polyModifier.polyModifierNode):
519  uvList = OpenMaya.MObject()
520 
521 
522  def __init__(self):
523  polyModifier.polyModifierNode.__init__(self)
524  self.fSplitUVFactory = splitUVFty()
525 
526 
527  def compute(self, plug, data):
528  """
529  Description:
530  This method computes the value of the given output plug based
531  on the values of the input attributes.
532 
533  Arguments:
534  plug - the plug to compute
535  data - object that provides access to the attributes for this node
536  """
537  stateData = 0
538  state = OpenMayaMPx.cvar.MPxNode_state
539  try:
540  stateData = data.outputValue(state)
541  except:
542  statusError("ERROR getting state")
543 
544  # Check for the HasNoEffect/PassThrough flag on the node.
545  #
546  # (stateData is an enumeration standard in all depend nodes - stored as short)
547  #
548  # (0 = Normal)
549  # (1 = HasNoEffect/PassThrough)
550  # (2 = Blocking)
551  # ...
552  #
553  if stateData.asShort() == 1:
554  try:
555  inputData = data.inputValue(splitUVNode.inMesh)
556  except:
557  statusError("ERROR getting inMesh")
558 
559  try:
560  outputData = data.outputValue(splitUVNode.outMesh)
561  except:
562  statusError("ERROR getting outMesh")
563 
564  # Simply redirect the inMesh to the outMesh for the PassThrough effect
565  #
566  outputData.setMObject(inputData.asMesh())
567  else:
568  # Check which output attribute we have been asked to
569  # compute. If this node doesn't know how to compute it,
570  # we must return MS::kUnknownParameter
571  #
572  if plug == splitUVNode.outMesh:
573  try:
574  inputData = data.inputValue(splitUVNode.inMesh)
575  except:
576  statusError("ERROR getting inMesh")
577 
578  try:
579  outputData = data.outputValue(splitUVNode.outMesh)
580  except:
581  statusError("ERROR getting outMesh")
582 
583  # Now, we get the value of the uvList and use it to perform
584  # the operation on this mesh
585  #
586  try:
587  inputUVs = data.inputValue(splitUVNode.uvList)
588  except:
589  statusError("ERROR getting uvList")
590 
591  # Copy the inMesh to the outMesh, and now you can
592  # perform operations in-place on the outMesh
593  #
594  outputData.setMObject(inputData.asMesh())
595  mesh = outputData.asMesh()
596 
597  # Retrieve the UV list from the component list.
598  #
599  # Note, we use a component list to store the components
600  # because it is more compact memory wise. (ie. comp[81:85]
601  # is smaller than comp[81], comp[82],...,comp[85])
602  #
603  compList = inputUVs.data()
604  compListFn = OpenMaya.MFnComponentListData(compList)
605 
606  uvIds = OpenMaya.MIntArray()
607  for i in range(compListFn.length()):
608  comp = compListFn[i]
609  if comp.apiType() == OpenMaya.MFn.kMeshMapComponent:
611  for j in range(uvComp.elementCount()):
612  uvId = uvComp.element(j)
613  uvIds.append(uvId)
614 
615  # Set the mesh object and uvList on the factory
616  #
617  self.fSplitUVFactory.setMesh(mesh)
618  self.fSplitUVFactory.setUVIds(uvIds)
619 
620  # Now, perform the splitUV
621  #
622  try:
623  self.fSplitUVFactory.doIt()
624  except:
625  statusError("ERROR in splitUVFty.doIt()")
626 
627  # Mark the output mesh as clean
628  #
629  outputData.setClean()
630  else:
631  return OpenMaya.kUnknownParameter
632 
633  return None
634 
635 
636 #####################################################################
637 ## REGISTRATION #####################################################
638 #####################################################################
639 
640 def cmdCreator():
641  return OpenMayaMPx.asMPxPtr(splitUV())
642 
643 
644 def nodeCreator():
645  return OpenMayaMPx.asMPxPtr(splitUVNode())
646 
647 
648 def nodeInitializer():
649  attrFn = OpenMaya.MFnTypedAttribute()
650 
651  splitUVNode.uvList = attrFn.create("inputComponents", "ics", OpenMaya.MFnComponentListData.kComponentList)
652  attrFn.setStorable(True) # To be stored during file-save
653 
654  splitUVNode.inMesh = attrFn.create("inMesh", "im", OpenMaya.MFnMeshData.kMesh)
655  attrFn.setStorable(True) # To be stored during file-save
656 
657  # Attribute is read-only because it is an output attribute
658  #
659  splitUVNode.outMesh = attrFn.create("outMesh", "om", OpenMaya.MFnMeshData.kMesh)
660  attrFn.setStorable(False)
661  attrFn.setWritable(False)
662 
663  # Add the attributes we have created to the node
664  #
665  splitUVNode.addAttribute(splitUVNode.uvList)
666  splitUVNode.addAttribute(splitUVNode.inMesh)
667  splitUVNode.addAttribute(splitUVNode.outMesh)
668 
669  # Set up a dependency between the input and the output. This will cause
670  # the output to be marked dirty when the input changes. The output will
671  # then be recomputed the next time the value of the output is requested.
672  #
673  splitUVNode.attributeAffects(splitUVNode.inMesh, splitUVNode.outMesh)
674  splitUVNode.attributeAffects(splitUVNode.uvList, splitUVNode.outMesh)
675 
676 
677 def initializePlugin(mobject):
678  mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
679  try:
680  mplugin.registerCommand(kPluginCmdName, cmdCreator)
681  except:
682  sys.stderr.write( "Failed to register command: %s\n" % kPluginCmdName)
683  raise
684 
685  try:
686  mplugin.registerNode(kPluginNodeTypeName, kPluginNodeId, nodeCreator, nodeInitializer)
687  except:
688  sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName)
689  raise
690 
691 
692 def uninitializePlugin(mobject):
693  mplugin = OpenMayaMPx.MFnPlugin(mobject)
694  try:
695  mplugin.deregisterCommand(kPluginCmdName)
696  except:
697  sys.stderr.write("Failed to unregister command: %s\n" % kPluginCmdName)
698  raise
699 
700  try:
701  mplugin.deregisterNode(kPluginNodeId)
702  except:
703  sys.stderr.write("Failed to deregister node: %s" % kPluginNodeTypeName)
704  raise