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