Python API 2.0 Reference
python/api1/py1MoveTool.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 commands "spMoveToolCmd" and "spMoveToolContext".
43 #
44 # Interactive tool for moving objects and components.
45 #
46 # This is an example of a selection-action tool. When nothing is selected, this
47 # tool behaves in exactly the same way as the selection tool in Maya. Once an object
48 # is selected, the tool turns into a translation tool.
49 #
50 # The plug-in can translate:
51 # - transforms
52 # - NURBS curve CVs
53 # - NURBS surface CVs
54 # - polygonal vertices
55 #
56 # This plug-in can only perform translation in orthographic views.
57 # Undo, redo, and journalling are supported by this tool.
58 #
59 # To use this plug-in, execute the following:
60 #
61 # import maya
62 # maya.cmds.loadPlugin("moveTool.py")
63 # maya.cmds.spMoveToolContext("spMoveToolContext1")
64 # shelfTopLevel = maya.mel.eval("global string $gShelfTopLevel;$temp = $gShelfTopLevel")
65 # maya.cmds.setParent("%s|General" % shelfTopLevel)
66 # maya.cmds.toolButton("spMoveTool1", cl="toolCluster", t="spMoveToolContext1", i1="moveTool.xpm")
67 # # Remove UI objects with
68 # maya.cmds.deleteUI("spMoveToolContext1")
69 # maya.cmds.deleteUI("spMoveTool1")
70 #
71 # This creates a new entry in the "Shelf1" tab of the tool shelf called "moveTool". Click the
72 # new icon, then select an object and drag it around in an orthographic view. The left mouse
73 # button allows movement in two directions, while the middle mouse button constrains the movement
74 # to a single direction.
75 #
76 # Note that you must have a Shelf1 tab before executing the commands.
77 #
78 ########################################################################
79 
80 #
81 # Creation Date: 2 October 2006
82 #
83 
84 from builtins import next
85 import maya.OpenMaya as OpenMaya
86 import maya.OpenMayaMPx as OpenMayaMPx
87 import maya.OpenMayaUI as OpenMayaUI
88 import sys, math
89 
90 kPluginCmdName="spMoveToolCmd"
91 kPluginCtxName="spMoveToolContext"
92 kVectorEpsilon = 1.0e-3
93 
94 # keep track of instances of MoveToolCmd to get around script limitation
95 # with proxy classes of base pointers that actually point to derived
96 # classes
97 kTrackingDictionary = {}
98 
99 # command
100 class MoveToolCmd(OpenMayaMPx.MPxToolCommand):
101  kDoIt, kUndoIt, kRedoIt = 0, 1, 2
102 
103  def __init__(self):
104  OpenMayaMPx.MPxToolCommand.__init__(self)
105  self.setCommandString(kPluginCmdName)
106  self.__delta = OpenMaya.MVector()
107  kTrackingDictionary[OpenMayaMPx.asHashable(self)] = self
108 
109  def __del__(self):
110  del kTrackingDictionary[OpenMayaMPx.asHashable(self)]
111 
112  def doIt(self, args):
113  argData = OpenMaya.MArgDatabase(self.syntax(), args)
114  vector = OpenMaya.MVector(1.0, 0.0, 0.0)
115  if args.length() == 1:
116  vector.x = args.asDouble(0)
117  elif args.length == 2:
118  vector.x = args.asDouble(0)
119  vector.y = args.asDouble(1)
120  elif args.length == 3:
121  vector.x = args.asDouble(0)
122  vector.y = args.asDouble(1)
123  vector.z = args.asDouble(2)
124  self.__delta = vector
125  self.__action(MoveToolCmd.kDoIt)
126 
127  def redoIt(self):
128  self.__action(MoveToolCmd.kRedoIt)
129 
130  def undoIt(self):
131  self.__action(MoveToolCmd.kUndoIt)
132 
133  def isUndoable(self):
134  return True
135 
136  def finalize(self):
137  """
138  Command is finished, construct a string for the command
139  for journalling.
140  """
141  command = OpenMaya.MArgList()
142  command.addArg(self.commandString())
143  command.addArg(self.__delta.x)
144  command.addArg(self.__delta.y)
145  command.addArg(self.__delta.z)
146 
147  # This call adds the command to the undo queue and sets
148  # the journal string for the command.
149  #
150  try:
151  OpenMayaMPx.MPxToolCommand._doFinalize(self, command)
152  except:
153  pass
154 
155  def setVector(self, x, y, z):
156  self.__delta.x = x
157  self.__delta.y = y
158  self.__delta.z = z
159 
160  def __action(self, flag):
161  """
162  Do the actual work here to move the objects by vector
163  """
164  if flag == MoveToolCmd.kUndoIt:
165  vector = -self.__delta
166  else:
167  vector = self.__delta
168 
169  # Create a selection list iterator
170  #
171  slist = OpenMaya.MSelectionList()
173  sIter = OpenMaya.MItSelectionList(slist)
174 
175  mdagPath = OpenMaya.MDagPath()
176  mComponent = OpenMaya.MObject()
177  spc = OpenMaya.MSpace.kWorld
178 
179  # Translate all selected objects
180  #
181  while not sIter.isDone():
182  # Get path and possibly a component
183  #
184  sIter.getDagPath(mdagPath, mComponent)
185  try:
186  transFn = OpenMaya.MFnTransform(mdagPath)
187  except:
188  pass
189  else:
190  try:
191  transFn.translateBy(vector, spc)
192  except:
193  sys.stderr.write("Error doing translate on transform\n")
194  next(sIter)
195  continue
196 
197  try:
198  cvFn = OpenMaya.MItCurveCV(mdagPath, mComponent)
199  except:
200  pass
201  else:
202  while not cvFn.isDone():
203  cvFn.translateBy(vector, spc)
204  next(cvFn)
205  cvFn.updateCurve()
206 
207  try:
208  sCvFn = OpenMaya.MItSurfaceCV(mdagPath, mComponent, True)
209  except:
210  pass
211 
212  else:
213  while not sCvFn.isDone():
214  while not CvFn.isRowDone():
215  sCvFn.translateBy(vector, spc)
216  next(sCvFn)
217  sCvFn.nextRow()
218  sCvFn.updateSurface()
219 
220  try:
221  vtxFn = OpenMaya.MItMeshVertex(mdagPath, mComponent)
222  except:
223  pass
224  else:
225  while not vtxFn.isDone():
226  vtxFn.translateBy(vector, spc)
227  next(vtxFn)
228  vtxFn.updateSurface()
229 
230  next(sIter)
231 
232 
233 class MoveContext(OpenMayaMPx.MPxSelectionContext):
234  kTop, kFront, kSide, kPersp = 0, 1, 2, 3
235 
236  def __init__(self):
237  OpenMayaMPx.MPxSelectionContext.__init__(self)
238  self._setTitleString("moveTool")
239  self.setImage("moveTool.xpm", OpenMayaMPx.MPxContext.kImage1)
240  self.__currWin = 0
241  self.__view = OpenMayaUI.M3dView()
242  self.__startPos_x = 0
243  self.__endPos_x = 0
244  self.__startPos_y = 0
245  self.__endPos_y = 0
246  self.__cmd = None
247 
248  def toolOnSetup(self, event):
249  self._setHelpString("drag to move selected object")
250 
251  def doPress(self, event):
252  OpenMayaMPx.MPxSelectionContext.doPress(self, event)
253  spc = OpenMaya.MSpace.kWorld
254 
255  # If we are not in selecting mode (i.e. an object has been selected)
256  # then set up for the translation.
257  #
258  if not self._isSelecting():
259  argX = OpenMaya.MScriptUtil(0)
260  argXPtr = argX.asShortPtr()
261  argY = OpenMaya.MScriptUtil(0)
262  argYPtr = argY.asShortPtr()
263  event.getPosition(argXPtr, argYPtr)
264  self.__startPos_x = argX.getShort(argXPtr)
265  self.__startPos_y = argY.getShort(argYPtr)
266  self.__view = OpenMayaUI.M3dView.active3dView()
267 
268  camera = OpenMaya.MDagPath()
269  self.__view.getCamera(camera)
270  fnCamera = OpenMaya.MFnCamera(camera)
271  upDir = fnCamera.upDirection(spc)
272  rightDir = fnCamera.rightDirection(spc)
273 
274  # Determine the camera used in the current view
275  #
276  if fnCamera.isOrtho():
277  if upDir.isEquivalent(OpenMaya.MVector.zNegAxis, kVectorEpsilon):
278  self.__currWin = MoveContext.kTop
279  elif rightDir.isEquivalent(OpenMaya.MVector.xAxis, kVectorEpsilon):
280  self.__currWin = MoveContext.kFront
281  else:
282  self.__currWin = MoveContext.kSide
283  else:
284  OpenMaya.MGlobal.displayWarning('moveTool only works in top, front and side views')
285  self.__currWin = MoveContext.kPersp
286 
287  # Create an instance of the move tool command.
288  #
289  newCmd = self._newToolCommand()
290  self.__cmd = kTrackingDictionary.get(OpenMayaMPx.asHashable(newCmd), None)
291  self.__cmd.setVector(0.0, 0.0, 0.0)
292 
293  def doDrag(self, event):
294  OpenMayaMPx.MPxSelectionContext.doDrag(self, event)
295 
296  # If we are not in selecting mode (i.e. an object has been selected)
297  # then do the translation.
298  #
299 
300  if not self._isSelecting():
301  argX = OpenMaya.MScriptUtil(0)
302  argXPtr = argX.asShortPtr()
303  argY = OpenMaya.MScriptUtil(0)
304  argYPtr = argY.asShortPtr()
305  event.getPosition(argXPtr, argYPtr)
306  self.__endPos_x = argX.getShort(argXPtr)
307  self.__endPos_y = argY.getShort(argYPtr)
308 
309  startW = OpenMaya.MPoint()
310  endW = OpenMaya.MPoint()
311  vec = OpenMaya.MVector()
312  self.__view.viewToWorld(self.__startPos_x, self.__startPos_y, startW, vec)
313  self.__view.viewToWorld(self.__endPos_x, self.__endPos_y, endW, vec)
314  downButton = event.mouseButton()
315 
316  # We reset the the move vector each time a drag event occurs
317  # and then recalculate it based on the start position.
318  #
319  self.__cmd.undoIt()
320  if self.__currWin == MoveContext.kTop:
321  if downButton == OpenMayaUI.MEvent.kMiddleMouse:
322  self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)
323  else:
324  self.__cmd.setVector(endW.x - startW.x, 0.0, endW.z - startW.z)
325 
326  elif self.__currWin == MoveContext.kFront:
327  if downButton == OpenMayaUI.MEvent.kMiddleMouse:
328 
329  self.__cmd.setVector(endW.x - startW.x, 0.0, 0.0)
330 
331  else:
332 
333  self.__cmd.setVector(endW.x - startW.x, endW.y - startW.y, 0.0)
334 
335  elif self.__currWin == MoveContext.kSide:
336  if downButton == OpenMayaUI.MEvent.kMiddleMouse:
337  self.__cmd.setVector(0.0, 0.0, endW.z - startW.z)
338  else:
339  self.__cmd.setVector(0.0, endW.y - startW.y, endW.z - startW.z)
340 
341  self.__cmd.redoIt()
342  self.__view.refresh(True)
343 
344  def doRelease(self, event):
345  OpenMayaMPx.MPxSelectionContext.doRelease(self, event)
346  if not self._isSelecting():
347  argX = OpenMaya.MScriptUtil(0)
348  argXPtr = argX.asShortPtr()
349  argY = OpenMaya.MScriptUtil(0)
350  argYPtr = argY.asShortPtr()
351  event.getPosition(argXPtr, argYPtr)
352  self.__endPos_x = argX.getShort(argXPtr)
353  self.__endPos_y = argY.getShort(argYPtr)
354 
355  # Delete the move command if we have moved less then 2 pixels
356  # otherwise call finalize to set up the journal and add the
357  # command to the undo queue.
358 
359  #
360  if (math.fabs(self.__startPos_x - self.__endPos_x) < 2 and
361  math.fabs(self.__startPos_y - self.__endPos_y) < 2):
362  self.__cmd = None
363  self.__view.refresh(True)
364  else:
365  self.__cmd.finalize()
366  self.__view.refresh(True)
367 
368  def doEnterRegion(self, event):
369  """
370  Print the tool description in the help line.
371  """
372  self._setHelpString("click on object and drag to move it")
373 
374 
375 #############################################################################
376 
377 
378 class MoveContextCommand(OpenMayaMPx.MPxContextCommand):
379  def __init__(self):
380  OpenMayaMPx.MPxContextCommand.__init__(self)
381 
382  def makeObj(self):
383  return OpenMayaMPx.asMPxPtr(MoveContext())
384 
385 def cmdCreator():
386  return OpenMayaMPx.asMPxPtr(MoveToolCmd())
387 
388 def ctxCmdCreator():
389  return OpenMayaMPx.asMPxPtr(MoveContextCommand())
390 
391 def syntaxCreator():
392  syntax = OpenMaya.MSyntax()
393  syntax.addArg(OpenMaya.MSyntax.kDouble)
394  syntax.addArg(OpenMaya.MSyntax.kDouble)
395  syntax.addArg(OpenMaya.MSyntax.kDouble)
396  return syntax
397 
398 # Initialize the script plug-in
399 
400 def initializePlugin(mobject):
401  mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "1.0", "Any")
402  try:
403  mplugin.registerContextCommand(kPluginCtxName, ctxCmdCreator, kPluginCmdName, cmdCreator, syntaxCreator)
404  except:
405  sys.stderr.write("Failed to register context command: %s\n" % kPluginCtxName)
406  raise
407 
408 # Uninitialize the script plug-in
409 def uninitializePlugin(mobject):
410  mplugin = OpenMayaMPx.MFnPlugin(mobject)
411  try:
412  mplugin.deregisterContextCommand(kPluginCtxName, kPluginCmdName)
413  except:
414  sys.stderr.write("Failed to deregister context command: %s\n" % kPluginCtxName)
415  raise
416