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