scripted/moveTool.py

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