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