Python API 2.0 Reference
python/api2/py2MoveTool.py
1 #-
2 # ===========================================================================
3 # Copyright 2021 Autodesk, Inc. All rights reserved.
4 #
5 # Use of this software is subject to the terms of the Autodesk license
6 # agreement provided at the time of installation or download, or which
7 # otherwise accompanies this software in either electronic or hard copy form.
8 # ===========================================================================
9 #+
10 
11 ########################################################################
12 # DESCRIPTION:
13 #
14 # Produces the Python commands "spMoveToolCmd" and "spMoveToolContext".
15 #
16 # Interactive tool for moving objects and components.
17 #
18 # This is an example of a selection-action tool. When nothing is selected, this
19 # tool behaves in exactly the same way as the selection tool in Maya. Once an object
20 # is selected, the tool turns into a translation tool.
21 #
22 # The plug-in can translate:
23 # - transforms
24 # - NURBS curve CVs
25 # - NURBS surface CVs
26 # - polygonal vertices
27 #
28 # This plug-in can only perform translation in orthographic views.
29 # Undo, redo, and journalling are supported by this tool.
30 #
31 # To use this plug-in, execute the following:
32 #
33 # import maya
34 # maya.cmds.loadPlugin("moveTool.py")
35 # maya.cmds.spMoveToolContext("spMoveToolContext1")
36 # shelfTopLevel = maya.mel.eval("global string $gShelfTopLevel;$temp = $gShelfTopLevel")
37 # maya.cmds.setParent("%s|General" % shelfTopLevel)
38 # maya.cmds.toolButton("spMoveTool1", cl="toolCluster", t="spMoveToolContext1", i1="moveTool.xpm")
39 # # Remove UI objects with
40 # maya.cmds.deleteUI("spMoveToolContext1")
41 # maya.cmds.deleteUI("spMoveTool1")
42 #
43 # This creates a new entry in the "Shelf1" tab of the tool shelf called "moveTool". Click the
44 # new icon, then select an object and drag it around in an orthographic view. The left mouse
45 # button allows movement in two directions, while the middle mouse button constrains the movement
46 # to a single direction.
47 #
48 # Note that you must have a Shelf1 tab before executing the commands.
49 #
50 ########################################################################
51 
52 from builtins import next
53 import maya.api.OpenMaya as om
54 import maya.api.OpenMayaUI as omui
55 import sys, math
56 
57 # tell Maya that we want to use Python API 2.0
58 maya_useNewAPI = True
59 
60 kPluginCmdName="spMoveToolCmd"
61 kPluginCtxName="spMoveToolContext"
62 kVectorEpsilon = 1.0e-3
63 kZNegAxis = ( 0, 0,-1)
64 
65 # command
66 class MoveToolCmd(omui.MPxToolCommand):
67  kDoIt, kUndoIt, kRedoIt = 0, 1, 2
68 
69  def __init__(self):
70  omui.MPxToolCommand.__init__(self)
71  self.commandString = kPluginCmdName
72  self.__delta = om.MVector()
73 
74  def doIt(self, args):
75  argData = om.MArgDatabase(self.syntax, args)
76  vector = om.MVector(1.0, 0.0, 0.0)
77  if args.length() == 1:
78  vector.x = args.asDouble(0)
79  elif args.length == 2:
80  vector.x = args.asDouble(0)
81  vector.y = args.asDouble(1)
82  elif args.length == 3:
83  vector.x = args.asDouble(0)
84  vector.y = args.asDouble(1)
85  vector.z = args.asDouble(2)
86  self.__delta = vector
87  self.__action(MoveToolCmd.kDoIt)
88 
89  def redoIt(self):
90  self.__action(MoveToolCmd.kRedoIt)
91 
92  def undoIt(self):
93  self.__action(MoveToolCmd.kUndoIt)
94 
95  def isUndoable(self):
96  return True
97 
98  def finalize(self):
99  """
100  Command is finished, construct a string for the command
101  for journalling.
102  """
103  command = om.MArgList()
104  command.addArg(self.commandString)
105  command.addArg(self.__delta.x)
106  command.addArg(self.__delta.y)
107  command.addArg(self.__delta.z)
108 
109  # This call adds the command to the undo queue and sets
110  # the journal string for the command.
111  #
112  try:
113  omui.MPxToolCommand.doFinalize(self, command)
114  except:
115  pass
116 
117  def setVector(self, x, y, z):
118  self.__delta.x = x
119  self.__delta.y = y
120  self.__delta.z = z
121 
122  def __action(self, flag):
123  """
124  Do the actual work here to move the objects by vector
125  """
126  if flag == MoveToolCmd.kUndoIt:
127  vector = -self.__delta
128  else:
129  vector = self.__delta
130 
131  # Create a selection list iterator
132  #
133  slist = om.MGlobal.getActiveSelectionList()
134  sIter = om.MItSelectionList(slist)
135 
136  mdagPath = om.MDagPath()
137  mComponent = om.MObject()
138  spc = om.MSpace.kWorld
139 
140  # Translate all selected objects
141  #
142  while not sIter.isDone():
143  # Get path and possibly a component
144  #
145  mdagPath = sIter.getDagPath()
146  mComponent = sIter.getComponent()
147  try:
148  transFn = om.MFnTransform(mdagPath)
149  except:
150  pass
151  else:
152  try:
153  transFn.translateBy(vector, spc)
154  except:
155  sys.stderr.write("Error doing translate on transform\n")
156  next(sIter)
157  continue
158 
159  try:
160  cvFn = om.MItCurveCV(mdagPath, mComponent)
161  except:
162  pass
163  else:
164  while not cvFn.isDone():
165  cvFn.translateBy(vector, spc)
166  next(cvFn)
167  cvFn.updateCurve()
168 
169  try:
170  sCvFn = om.MItSurfaceCV(mdagPath, mComponent, True)
171  except:
172  pass
173 
174  else:
175  while not sCvFn.isDone():
176  while not CvFn.isRowDone():
177  sCvFn.translateBy(vector, spc)
178  next(sCvFn)
179  sCvFn.nextRow()
180  sCvFn.updateSurface()
181 
182  try:
183  vtxFn = om.MItMeshVertex(mdagPath, mComponent)
184  except:
185  pass
186  else:
187  while not vtxFn.isDone():
188  vtxFn.translateBy(vector, spc)
189  next(vtxFn)
190  vtxFn.updateSurface()
191 
192  next(sIter)
193 
194 
195 class MoveContext(omui.MPxSelectionContext):
196  kTop, kFront, kSide, kPersp = 0, 1, 2, 3
197 
198  def __init__(self):
199  omui.MPxSelectionContext.__init__(self)
200  self.setTitleString("moveTool")
201  self.setImage("moveTool.xpm", omui.MPxContext.kImage1)
202  self.__currWin = 0
203  self.__view = omui.M3dView()
204  self.__startPos_x = 0
205  self.__endPos_x = 0
206  self.__startPos_y = 0
207  self.__endPos_y = 0
208  self.__cmd = None
209 
210  def toolOnSetup(self, event):
211  self.setHelpString("drag to move selected object")
212 
213  def doPress(self, event, drawMgr, context):
214  omui.MPxSelectionContext.doPress(self, event, drawMgr, context)
215  spc = om.MSpace.kWorld
216 
217  # If we are not in selecting mode (i.e. an object has been selected)
218  # then set up for the translation.
219  #
220  if not self.isSelecting():
221  self.__startPos_x, self.__startPos_y = event.position
222  self.__view = omui.M3dView.active3dView()
223 
224  camera = self.__view.getCamera()
225  fnCamera = om.MFnCamera(camera)
226  upDir = fnCamera.upDirection(spc)
227  rightDir = fnCamera.rightDirection(spc)
228 
229  # Determine the camera used in the current view
230  #
231  if fnCamera.isOrtho():
232  if upDir.isEquivalent(om.MVector.kZnegAxisVector, kVectorEpsilon):
233  self.__currWin = MoveContext.kTop
234  elif rightDir.isEquivalent(om.MVector.kXaxisVector, kVectorEpsilon):
235  self.__currWin = MoveContext.kFront
236  else:
237  self.__currWin = MoveContext.kSide
238  else:
239  om.MGlobal.displayWarning('moveTool only works in top, front and side views')
240  self.__currWin = MoveContext.kPersp
241 
242  # Create an instance of the move tool command.
243  #
244  self.__cmd = MoveToolCmd()
245  self.__cmd.setVector(0.0, 0.0, 0.0)
246 
247  def doDrag(self, event, drawMgr, context):
248  omui.MPxSelectionContext.doDrag(self, event, drawMgr, context)
249 
250  # If we are not in selecting mode (i.e. an object has been selected)
251  # then do the translation.
252  #
253 
254  if not self.isSelecting():
255  self.__endPos_x, self.__endPos_y = event.position
256 
257  startW = om.MPoint()
258  endW = om.MPoint()
259  vec = om.MVector()
260  self.__view.viewToWorld(self.__startPos_x, self.__startPos_y, startW, vec)
261  self.__view.viewToWorld(self.__endPos_x, self.__endPos_y, endW, vec)
262  downButton = event.mouseButton()
263 
264  # We reset the the move vector each time a drag event occurs
265  # and then recalculate it based on the start position.
266  #
267  self.__cmd.undoIt()
268  if self.__currWin == MoveContext.kTop:
269  if downButton == omui.MEvent.kMiddleMouse:
270  self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)
271  else:
272  self.__cmd.setVector(endW.x - startW.x, 0.0, endW.z - startW.z)
273 
274  elif self.__currWin == MoveContext.kFront:
275  if downButton == omui.MEvent.kMiddleMouse:
276 
277  self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)
278 
279  else:
280 
281  self.__cmd.setVector(endW.x - startW.x, endW.y - startW.y, 0.0)
282 
283  elif self.__currWin == MoveContext.kSide:
284  if downButton == omui.MEvent.kMiddleMouse:
285  self.__cmd.setVector(0.0, 0.0, endW.z - startW.z)
286  else:
287  self.__cmd.setVector(0.0, endW.y - startW.y, endW.z - startW.z)
288 
289  self.__cmd.redoIt()
290  self.__view.refresh(True)
291 
292  def doRelease(self, event, drawMgr, context):
293  omui.MPxSelectionContext.doRelease(self, event, drawMgr, context)
294  if not self.isSelecting():
295  self.__endPos_x, self.__endPos_y = event.position
296 
297  # Delete the move command if we have moved less then 2 pixels
298  # otherwise call finalize to set up the journal and add the
299  # command to the undo queue.
300 
301  #
302  if (math.fabs(self.__startPos_x - self.__endPos_x) < 2 and
303  math.fabs(self.__startPos_y - self.__endPos_y) < 2):
304  self.__cmd = None
305  self.__view.refresh(True)
306  else:
307  self.__cmd.finalize()
308  self.__view.refresh(True)
309 
310  def doEnterRegion(self, event):
311  """
312  Print the tool description in the help line.
313  """
314  self.setHelpString("click on object and drag to move it")
315 
316 
317 #############################################################################
318 
319 
320 class MoveContextCommand(omui.MPxContextCommand):
321  def __init__(self):
322  omui.MPxContextCommand.__init__(self)
323 
324  def makeObj(self):
325  return MoveContext()
326 
327 def cmdCreator():
328  return MoveToolCmd()
329 
330 def ctxCmdCreator():
331  return MoveContextCommand()
332 
333 def syntaxCreator():
334  syntax = om.MSyntax()
335  syntax.addArg(om.MSyntax.kDouble)
336  syntax.addArg(om.MSyntax.kDouble)
337  syntax.addArg(om.MSyntax.kDouble)
338  return syntax
339 
340 # Initialize the script plug-in
341 
342 def initializePlugin(mobject):
343  mplugin = om.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
344  try:
345  mplugin.registerCommand(kPluginCmdName, cmdCreator, syntaxCreator)
346  mplugin.registerContextCommand(kPluginCtxName, ctxCmdCreator)
347  except:
348  sys.stderr.write("Failed to register context command: %s\n" % kPluginCtxName)
349  raise
350 
351 # Uninitialize the script plug-in
352 def uninitializePlugin(mobject):
353  mplugin = om.MFnPlugin(mobject)
354  try:
355  mplugin.deregisterCommand(kPluginCmdName)
356  mplugin.deregisterContextCommand(kPluginCtxName)
357  except:
358  sys.stderr.write("Failed to deregister context command: %s\n" % kPluginCtxName)
359  raise
360