scripted/basicShape.py

scripted/basicShape.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 a surface shape node "spBasicShape".
43 #
44 # This plug-in implements a proxy surface shape node that will display rectangles,
45 # triangles, and circles using standard OpenGL calls.
46 #
47 # There are no output attributes for this shape. The following input attributes
48 # define the type of shape to draw:
49 #
50 # shapeType : 0=rectangle, 1=circle, 2=triangle
51 # radius : circle radius
52 # height : rectangle and triangle height
53 # width : rectangle and triangle width
54 #
55 # To create one of these nodes, enter the following commands after the plug-in is loaded:
56 #
57 # import maya.cmds as cmds
58 # cmds.createNode("spBasicShape")
59 #
60 # An object will be created with reference to the node. By default, it will be a rectangle.
61 # Use the different node options to change the type of shape or its size and shape.
62 # Add textures or manipulate as with any other object.
63 #
64 ################################################################################
65 
66 import maya.OpenMaya as OpenMaya
67 import maya.OpenMayaMPx as OpenMayaMPx
68 import maya.OpenMayaRender as OpenMayaRender
69 import maya.OpenMayaUI as OpenMayaUI
70 
71 import math
72 import sys
73 
74 
75 kPluginNodeTypeName = "spBasicShape"
76 spBasicShapeNodeId = OpenMaya.MTypeId(0x87018)
77 
78 glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()
79 glFT = glRenderer.glFunctionTable()
80 
81 kLeadColor = 18 # green
82 kActiveColor = 15 # white
83 kActiveAffectedColor = 8 # purple
84 kDormantColor = 4 # blue
85 kHiliteColor = 17 # pale blue
86 
87 kDefaultRadius = 1.0
88 kDefaultHeight = 2.0
89 kDefaultWidth = 2.0
90 kDefaultShapeType = 0
91 
92 
93 #####################################################################
94 ##
95 ## Geometry class
96 ##
97 class basicGeom:
98  radius = kDefaultRadius
99  height = kDefaultHeight
100  width = kDefaultWidth
101  shapeType = kDefaultShapeType
102 
103 
104 #####################################################################
105 ##
106 ## Shape class - defines the non-UI part of a shape node
107 ##
108 class basicShape(OpenMayaMPx.MPxSurfaceShape):
109  def __init__(self):
110  OpenMayaMPx.MPxSurfaceShape.__init__(self)
111 
112  # class variables
113  aShapeType = OpenMaya.MObject()
114  aRadius = OpenMaya.MObject()
115  aHeight = OpenMaya.MObject()
116  aWidth = OpenMaya.MObject()
117 
118  # geometry
119  self.__myGeometry = basicGeom()
120 
121 
122  # override
123  def postConstructor(self):
124  """
125  When instances of this node are created internally, the MObject associated
126  with the instance is not created until after the constructor of this class
127  is called. This means that no member functions of MPxSurfaceShape can
128  be called in the constructor.
129  The postConstructor solves this problem. Maya will call this function
130  after the internal object has been created.
131  As a general rule do all of your initialization in the postConstructor.
132  """
133  self.setRenderable(True)
134 
135 
136  # override
137  def compute(self, plug, dataBlock):
138  """
139  Since there are no output attributes this is not necessary but
140  if we wanted to compute an output mesh for rendering it would
141  be done here base on the inputs.
142  """
143  return OpenMaya.kUnknownParameter
144 
145 
146  # override
147  def getInternalValue(self, plug, datahandle):
148  """
149  Handle internal attributes.
150  In order to impose limits on our attribute values we
151  mark them internal and use the values in fGeometry intead.
152  """
153  if (plug == basicShape.aRadius):
154  datahandle.setDouble(self.__myGeometry.radius)
155 
156  elif (plug == basicShape.aHeight):
157  datahandle.setDouble(self.__myGeometry.height)
158 
159  elif (plug == basicShape.aWidth):
160  datahandle.setDouble(self.__myGeometry.width)
161 
162  else:
163  return OpenMayaMPx.MPxSurfaceShape.getInternalValue(self, plug, datahandle)
164 
165  return True
166 
167 
168  # override
169  def setInternalValue(self, plug, datahandle):
170  """
171  Handle internal attributes.
172  In order to impose limits on our attribute values we
173  mark them internal and use the values in fGeometry intead.
174  """
175 
176  # the minimum radius is 0
177  #
178  if (plug == basicShape.aRadius):
179  radius = datahandle.asDouble()
180 
181  if (radius < 0):
182  radius = 0
183 
184  self.__myGeometry.radius = radius
185 
186  elif (plug == basicShape.aHeight):
187  val = datahandle.asDouble()
188  if (val <= 0):
189  val = 0.1
190  self.__myGeometry.height = val
191 
192  elif (plug == basicShape.aWidth):
193  val = datahandle.asDouble()
194  if (val <= 0):
195  val = 0.1
196  self.__myGeometry.width = val
197 
198  else:
199  return OpenMayaMPx.MPxSurfaceShape.setInternalValue(self, plug, datahandle)
200 
201  return True
202 
203 
204  # override
205  def isBounded(self):
206  return True
207 
208 
209  # override
210  def boundingBox(self):
211  """
212  Returns the bounding box for the shape.
213  In this case just use the radius and height attributes
214  to determine the bounding box.
215  """
216  result = OpenMaya.MBoundingBox()
217 
218  geom = self.geometry()
219 
220  r = geom.radius
221  result.expand(OpenMaya.MPoint(r,r,r))
222  result.expand(OpenMaya.MPoint(-r,-r,-r))
223 
224  r = geom.height/2.0
225  result.expand(OpenMaya.MPoint(r,r,r))
226  result.expand(OpenMaya.MPoint(-r,-r,-r))
227 
228  r = geom.width/2.0
229  result.expand(OpenMaya.MPoint(r,r,r))
230  result.expand(OpenMaya.MPoint(-r,-r,-r))
231 
232  return result
233 
234 
235  def geometry(self):
236  """
237  This function gets the values of all the attributes and
238  assigns them to the fGeometry. Calling MPlug::getValue
239  will ensure that the values are up-to-date.
240  """
241  # return self.__myGeometry
242 
243  this_object = self.thisMObject()
244 
245  plug = OpenMaya.MPlug(this_object, basicShape.aRadius)
246  self.__myGeometry.radius = plug.asDouble()
247 
248  plug.setAttribute(basicShape.aHeight)
249  self.__myGeometry.height = plug.asDouble()
250 
251  plug.setAttribute(basicShape.aWidth)
252  self.__myGeometry.width = plug.asDouble()
253 
254  plug.setAttribute(basicShape.aShapeType)
255  self.__myGeometry.shapeType = plug.asShort() # enum????
256 
257  return self.__myGeometry
258 
259 def printMsg(msg):
260  print msg
261  stream=OpenMaya.MStreamUtils.stdOutStream()
262  OpenMaya.MStreamUtils.writeCharBuffer(stream,msg)
263 
264 #####################################################################
265 ##
266 ## UI class - defines the UI part of a shape node
267 ##
268 class basicShapeUI(OpenMayaMPx.MPxSurfaceShapeUI):
269  # private enums
270  __kDrawRectangle, __kDrawCircle, __kDrawTriangle = range(3)
271  __kDrawWireframe, __kDrawWireframeOnShaded, __kDrawSmoothShaded, __kDrawFlatShaded, __kLastToken = range(5)
272 
273  def __init__(self):
274  OpenMayaMPx.MPxSurfaceShapeUI.__init__(self)
275 
276 
277  # override
278  def getDrawRequests(self, info, objectAndActiveOnly, queue):
279  """
280  The draw data is used to pass geometry through the
281  draw queue. The data should hold all the information
282  needed to draw the shape.
283  """
284  data = OpenMayaUI.MDrawData()
285  # printMsg("**before getProtoype\n");
286  request = info.getPrototype(self)
287  # printMsg("**after getProtoype\n");
288  shapeNode = self.surfaceShape()
289  geom = shapeNode.geometry()
290  self.getDrawData(geom, data)
291  request.setDrawData(data)
292 
293  # Are we displaying meshes?
294  if (not info.objectDisplayStatus(OpenMayaUI.M3dView.kDisplayMeshes)):
295  return
296 
297  # Use display status to determine what color to draw the object
298  if (info.displayStyle() == OpenMayaUI.M3dView.kWireFrame):
299  self.getDrawRequestsWireframe(request, info)
300  queue.add(request)
301 
302  elif (info.displayStyle() == OpenMayaUI.M3dView.kGouraudShaded):
303  request.setToken(basicShapeUI.__kDrawSmoothShaded)
304  self.getDrawRequestsShaded(request, info, queue, data)
305  queue.add(request)
306 
307  elif (info.displayStyle() == OpenMayaUI.M3dView.kFlatShaded):
308  request.setToken(basicShapeUI.__kDrawFlatShaded)
309  self.getDrawRequestsShaded(request, info, queue, data)
310  queue.add(request)
311  return
312 
313 
314  # override
315  def draw(self, request, view):
316  """
317  From the given draw request, get the draw data and determine
318  which basic to draw and with what values.
319  """
320 
321  data = request.drawData()
322  shapeNode = self.surfaceShape()
323  geom = shapeNode.geometry()
324  token = request.token()
325  drawTexture = False
326 
327  #set up texturing if it is shaded
328  if ((token == basicShapeUI.__kDrawSmoothShaded) or
329  (token == basicShapeUI.__kDrawFlatShaded)):
330  # Set up the material
331  material = request.material()
332  material.setMaterial(request.multiPath(), request.isTransparent())
333 
334  # Enable texturing
335  #
336  # Note, Maya does not enable texturing when drawing with the
337  # default material. However, your custom shape is free to ignore
338  # this setting.
339  #
340  drawTexture = material.materialIsTextured() and not view.usingDefaultMaterial()
341 
342  # Apply the texture to the current view
343  if (drawTexture):
344  material.applyTexture(view, data)
345 
346  glFT.glPushAttrib( OpenMayaRender.MGL_ALL_ATTRIB_BITS )
347 
348  if ((token == basicShapeUI.__kDrawSmoothShaded) or
349  (token == basicShapeUI.__kDrawFlatShaded)):
350  glFT.glEnable(OpenMayaRender.MGL_POLYGON_OFFSET_FILL)
351  glFT.glPolygonMode(OpenMayaRender.MGL_FRONT_AND_BACK, OpenMayaRender.MGL_FILL)
352  if (drawTexture):
353  glFT.glEnable(OpenMayaRender.MGL_TEXTURE_2D)
354  else:
355  glFT.glPolygonMode(OpenMayaRender.MGL_FRONT_AND_BACK, OpenMayaRender.MGL_LINE)
356 
357  # draw the shapes
358  if (geom.shapeType == basicShapeUI.__kDrawCircle):
359  # circle
360  glFT.glBegin(OpenMayaRender.MGL_POLYGON)
361  for i in range(0,360):
362  rad = (i*2*math.pi)/360;
363  glFT.glNormal3f(0.0, 0.0, 1.0)
364  if (i == 360):
365  glFT.glTexCoord3f(geom.radius*math.cos(0), geom.radius*math.sin(0), 0.0)
366  glFT.glVertex3f(geom.radius*math.cos(0), geom.radius*math.sin(0), 0.0)
367  else:
368  glFT.glTexCoord3f(geom.radius*math.cos(rad), geom.radius*math.sin(rad), 0.0)
369  glFT.glVertex3f(geom.radius*math.cos(rad), geom.radius*math.sin(rad), 0.0)
370  glFT.glEnd()
371 
372  elif (geom.shapeType == basicShapeUI.__kDrawRectangle):
373  #rectangle
374  glFT.glBegin(OpenMayaRender.MGL_QUADS)
375 
376  glFT.glTexCoord2f(-1*(geom.width/2), -1*(geom.height/2))
377  glFT.glVertex3f(-1*(geom.width/2), -1*(geom.height/2), 0.0)
378  glFT.glNormal3f(0, 0, 1.0)
379 
380  glFT.glTexCoord2f(-1*(geom.width/2), (geom.height/2))
381  glFT.glVertex3f(-1*(geom.width/2), (geom.height/2), 0.0)
382  glFT.glNormal3f(0, 0, 1.0)
383 
384  glFT.glTexCoord2f((geom.width/2), (geom.height/2))
385  glFT.glVertex3f((geom.width/2), (geom.height/2), 0.0)
386  glFT.glNormal3f(0, 0, 1.0)
387 
388  glFT.glTexCoord2f((geom.width/2), -1*(geom.height/2))
389  glFT.glVertex3f((geom.width/2), -1*(geom.height/2), 0.0)
390  glFT.glNormal3f(0, 0, 1.0)
391  glFT.glEnd()
392 
393  else:
394  # triangle
395  glFT.glBegin(OpenMayaRender.MGL_TRIANGLES)
396  glFT.glTexCoord2f(-1*(geom.width/2), -1*(geom.height/2))
397  glFT.glVertex3f(-1*(geom.width/2), -1*(geom.height/2), 0.0)
398  glFT.glNormal3f(0.0, 0.0, 1.0)
399 
400  glFT.glTexCoord2f(0.0, (geom.height/2))
401  glFT.glVertex3f(0.0, (geom.height/2), 0.0)
402  glFT.glNormal3f(0.0, 0.0, 1.0)
403 
404  glFT.glTexCoord2f((geom.width/2), -1*(geom.height/2))
405  glFT.glVertex3f((geom.width/2), -1*(geom.height/2), 0.0)
406  glFT.glNormal3f(0.0, 0.0, 1.0)
407  glFT.glEnd()
408 
409  if ((token == basicShapeUI.__kDrawSmoothShaded) or
410  (token == basicShapeUI.__kDrawFlatShaded)):
411  glFT.glDisable(OpenMayaRender.MGL_POLYGON_OFFSET_FILL)
412  # Turn off texture mode
413  if (drawTexture):
414  glFT.glDisable(OpenMayaRender.MGL_TEXTURE_2D)
415 
416  glFT.glPopAttrib()
417 
418 
419 
420  # override
421  def select(self, selectInfo, selectionList, worldSpaceSelectPts):
422  """
423  Select function. Gets called when the bbox for the object is selected.
424  This function just selects the object without doing any intersection tests.
425  """
426 
427  priorityMask = OpenMaya.MSelectionMask(OpenMaya.MSelectionMask.kSelectObjectsMask)
428  item = OpenMaya.MSelectionList()
429  item.add(selectInfo.selectPath())
430  xformedPt = OpenMaya.MPoint()
431  selectInfo.addSelection(item, xformedPt, selectionList,
432  worldSpaceSelectPts, priorityMask, False)
433  return True
434 
435 
436  def getDrawRequestsWireframe(self, request, info):
437 
438  request.setToken(basicShapeUI.__kDrawWireframe)
439 
440  displayStatus = info.displayStatus()
441  activeColorTable = OpenMayaUI.M3dView.kActiveColors
442  dormantColorTable = OpenMayaUI.M3dView.kDormantColors
443 
444  if (displayStatus == OpenMayaUI.M3dView.kLead):
445  request.setColor(kLeadColor, activeColorTable)
446 
447  elif (displayStatus == OpenMayaUI.M3dView.kActive):
448  request.setColor(kActiveColor, activeColorTable)
449 
450  elif (displayStatus == OpenMayaUI.M3dView.kActiveAffected):
451  request.setColor(kActiveAffectedColor, activeColorTable)
452 
453  elif (displayStatus == OpenMayaUI.M3dView.kDormant):
454  request.setColor(kDormantColor, dormantColorTable)
455 
456  elif (displayStatus == OpenMayaUI.M3dView.kHilite):
457  request.setColor(kHiliteColor, activeColorTable)
458 
459 
460 
461  def getDrawRequestsShaded(self, request, info, queue, data):
462  # Need to get the material info
463  path = info.multiPath() # path to your dag object
464  view = info.view() # view to draw to
465  material = OpenMayaMPx.MPxSurfaceShapeUI.material(self, path)
466  usingDefaultMat = view.usingDefaultMaterial()
467  if usingDefaultMat:
469 
470  displayStatus = info.displayStatus()
471 
472  # Evaluate the material and if necessary, the texture.
473  try:
474  material.evaluateMaterial(view, path)
475  except RuntimeError:
476  print "Couldn't evaluate material"
477  raise
478 
479  drawTexture = not usingDefaultMat
480  if (drawTexture and material.materialIsTextured()):
481  material.evaluateTexture(data)
482 
483  request.setMaterial(material)
484 
485  #materialTransparent = False
486  #material.getHasTransparency(materialTransparent)
487  #if (materialTransparent):
488  # request.setIsTransparent(True)
489 
490  # create a draw request for wireframe on shaded if necessary.
491  if ((displayStatus == OpenMayaUI.M3dView.kActive) or
492  (displayStatus == OpenMayaUI.M3dView.kLead) or
493  (displayStatus == OpenMayaUI.M3dView.kHilite)):
494  wireRequest = info.getPrototype(self)
495  wireRequest.setDrawData(data)
496  self.getDrawRequestsWireframe(wireRequest, info)
497  wireRequest.setToken(basicShapeUI.__kDrawWireframeOnShaded)
498  wireRequest.setDisplayStyle(OpenMayaUI.M3dView.kWireFrame)
499  queue.add(wireRequest)
500 
501 #####################################################################
502 
503 def nodeCreator():
504  return OpenMayaMPx.asMPxPtr( basicShape() )
505 
506 
507 def uiCreator():
508  return OpenMayaMPx.asMPxPtr( basicShapeUI() )
509 
510 
511 def nodeInitializer():
512  # BASIC type enumerated attribute
513  enumAttr = OpenMaya.MFnEnumAttribute()
514  basicShape.aShapeType = enumAttr.create("shapeType", "st", kDefaultShapeType)
515  enumAttr.addField("rectangle", 0)
516  enumAttr.addField("circle", 1)
517  enumAttr.addField("triangle", 2)
518  enumAttr.setHidden(False)
519  enumAttr.setKeyable(True)
520  basicShape.addAttribute(basicShape.aShapeType)
521 
522  # BASIC numeric attributes
523  # utility func for numeric attrs
524  def setOptions(attr):
525  attr.setHidden(False)
526  attr.setKeyable(True)
527  attr.setInternal(True)
528 
529  numericAttr = OpenMaya.MFnNumericAttribute()
530 
531  basicShape.aRadius = numericAttr.create("radius", "r", OpenMaya.MFnNumericData.kDouble, kDefaultRadius)
532  setOptions(numericAttr)
533  basicShape.addAttribute(basicShape.aRadius)
534 
535  basicShape.aHeight = numericAttr.create("height", "ht", OpenMaya.MFnNumericData.kDouble, kDefaultHeight)
536  setOptions(numericAttr)
537  basicShape.addAttribute(basicShape.aHeight)
538 
539  basicShape.aWidth = numericAttr.create("width2", "wt2", OpenMaya.MFnNumericData.kDouble, kDefaultWidth)
540  setOptions(numericAttr)
541  basicShape.addAttribute(basicShape.aWidth)
542 
543 # initialize the script plug-in
544 def initializePlugin(mobject):
545  mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "8.5", "Any")
546  try:
547  mplugin.registerShape( kPluginNodeTypeName, spBasicShapeNodeId,
548  nodeCreator, nodeInitializer, uiCreator )
549  except:
550  sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )
551  raise
552 
553 
554 # uninitialize the script plug-in
555 def uninitializePlugin(mobject):
556  mplugin = OpenMayaMPx.MFnPlugin(mobject)
557  try:
558  mplugin.deregisterNode( spBasicShapeNodeId )
559  except:
560  sys.stderr.write( "Failed to deregister node: %s" % kPluginNodeTypeName )
561  raise