Python API 2.0 Reference
python/api1/py1InstanceShape.py
1 from __future__ import division
2 #-
3 # ==========================================================================
4 # Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors. All
5 # rights reserved.
6 #
7 # The coded instructions, statements, computer programs, and/or related
8 # material (collectively the "Data") in these files contain unpublished
9 # information proprietary to Autodesk, Inc. ("Autodesk") and/or its
10 # licensors, which is protected by U.S. and Canadian federal copyright
11 # law and by international treaties.
12 #
13 # The Data is provided for use exclusively by You. You have the right
14 # to use, modify, and incorporate this Data into other products for
15 # purposes authorized by the Autodesk software license agreement,
16 # without fee.
17 #
18 # The copyright notices in the Software and this entire statement,
19 # including the above license grant, this restriction and the
20 # following disclaimer, must be included in all copies of the
21 # Software, in whole or in part, and all derivative works of
22 # the Software, unless such copies or derivative works are solely
23 # in the form of machine-executable object code generated by a
24 # source language processor.
25 #
26 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
27 # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
28 # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
29 # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
30 # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
31 # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
32 # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
33 # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
34 # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
35 # OR PROBABILITY OF SUCH DAMAGES.
36 #
37 # ==========================================================================
38 #+
39 
40 ###############################################################################
41 ##
42 ## instanceShape.py
43 ##
44 ## Description:
45 ## Registers a new shape that acts like an instancer. The new shape
46 ## type is called "instanceShape".
47 ##
48 ## The shape will instance N copies of a shape connected via a message
49 ## attribute on the node. The sample will distribute these N copies
50 ## in the XZ plane.
51 ##
52 ## There are no output attributes for this shape.
53 ## The following input attributes define the type of shape to draw.
54 ##
55 ## radius : circle radius for instance object.
56 ## instanceShape : a connection to the shape to instance
57 ## count : number of instances to make.
58 ##
59 ## Additionally the instancing feature demonstrated in this code
60 ## only works for custom shapes. Non-custom shapes will not work.
61 ##
62 ################################################################################
63 
64 # Usage:
65 # import maya
66 # maya.cmds.loadPlugin("instanceShape.py")
67 # maya.cmds.loadPlugin("basicShape.py")
68 
69 # basicShape = maya.cmds.createNode("spBasicShape")
70 # instanceShape = maya.cmds.createNode("spInstanceShape")
71 # maya.cmds.connectAttr( basicShape + ".message", instanceShape + ".instanceShape" )
72 #
73 
74 from builtins import object
75 from builtins import range
76 import maya.OpenMaya as OpenMaya
77 import maya.OpenMayaMPx as OpenMayaMPx
78 import maya.OpenMayaRender as OpenMayaRender
79 import maya.OpenMayaUI as OpenMayaUI
80 
81 import math
82 import sys
83 
84 kPluginNodeTypeName = "spInstanceShape"
85 spInstanceShapeNodeId = OpenMaya.MTypeId(0x00080068)
86 
87 glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer()
88 glFT = glRenderer.glFunctionTable()
89 
90 kLeadColor = 18 # green
91 kActiveColor = 15 # white
92 kActiveAffectedColor = 8 # purple
93 kDormantColor = 4 # blue
94 kHiliteColor = 17 # pale blue
95 
96 kDefaultRadius = 1.0
97 kDefaultCount = 10
98 
99 
100 #####################################################################
101 ##
102 ## Geometry class
103 ##
104 class instanceGeom(object):
105  def __init__(self):
106  self.radius = kDefaultRadius
107  self.count = kDefaultCount
108  self.instanceShape = None
109  self.drawQueueList = []
110 
111 #####################################################################
112 ##
113 ## Shape class - defines the non-UI part of a shape node
114 ##
115 class instanceShape(OpenMayaMPx.MPxSurfaceShape):
116  # class variables
117  aRadius = OpenMaya.MObject()
118  aCount = OpenMaya.MObject()
119  aInstanceShape = OpenMaya.MObject()
120 
121  def __init__(self):
122  OpenMayaMPx.MPxSurfaceShape.__init__(self)
123 
124  # geometry
125  self.__myGeometry = instanceGeom()
126 
127  # override
128  def postConstructor(self):
129  """
130  When instances of this node are created internally, the
131  MObject associated with the instance is not created until
132  after the constructor of this class is called. This means
133  that no member functions of MPxSurfaceShape can be called in
134  the constructor. The postConstructor solves this
135  problem. Maya will call this function after the internal
136  object has been created. As a general rule do all of your
137  initialization in the postConstructor.
138  """
139  self.setRenderable(True)
140 
141  # override
142  def getInternalValue(self, plug, datahandle):
143  """
144  Handle internal attributes.
145  In order to impose limits on our attribute values we
146  mark them internal and use the values in fGeometry instead.
147  """
148  if (plug == instanceShape.aRadius):
149  datahandle.setDouble(self.__myGeometry.radius)
150  elif (plug == instanceShape.aCount):
151  datahandle.setInt(self.__myGeometry.count)
152  else:
153  return OpenMayaMPx.MPxSurfaceShape.getInternalValue(self, plug, datahandle)
154 
155  return True
156 
157 
158  # override
159  def setInternalValue(self, plug, datahandle):
160  """
161  Handle internal attributes.
162  In order to impose limits on our attribute values we
163  mark them internal and use the values in fGeometry instead.
164  """
165 
166  # the minimum radius is 0
167  #
168  if (plug == instanceShape.aRadius):
169  radius = datahandle.asDouble()
170 
171  if (radius < 0):
172  radius = 0
173 
174  self.__myGeometry.radius = radius
175 
176  elif (plug == instanceShape.aCount):
177  count = datahandle.asInt()
178  if (count < 0):
179  count = 0
180  self.__myGeometry.count = count
181  else:
182  return OpenMayaMPx.MPxSurfaceShape.setInternalValue(self, plug, datahandle)
183  return True
184 
185 
186  # override
187  def isBounded(self):
188  return True
189 
190 
191  # override
192  def boundingBox(self):
193  """
194  Returns the bounding box for the shape.
195  In this case just use the radius and height attributes
196  to determine the bounding box.
197  """
198  result = OpenMaya.MBoundingBox()
199 
200  geom = self.geometry()
201 
202  # Include the instance shape bounding box
203  if geom.instanceShape:
204  fnDag = OpenMaya.MFnDagNode( geom.instanceShape )
205  result = fnDag.boundingBox()
206 
207  r = geom.radius
208  instanceBbox = OpenMaya.MBoundingBox( result )
209  for c in range( geom.count ):
210  percent = float(c)/float(geom.count)
211  rad = 2*math.pi * percent
212  p = (r*math.cos(rad), r*math.sin(rad),0.0)
213  newbbox = OpenMaya.MBoundingBox( instanceBbox )
215  vec = OpenMaya.MVector( p[0], p[1], p[2] )
216  trans.setTranslation( vec, OpenMaya.MSpace.kTransform )
217  mmatrix = trans.asMatrix();
218  newbbox.transformUsing( mmatrix )
219  result.expand( newbbox )
220 
221  return result
222 
223 
224  def geometry(self):
225  """
226  This function gets the values of all the attributes and
227  assigns them to the fGeometry. Calling MPlug::getValue
228  will ensure that the values are up-to-date.
229  """
230  # return self.__myGeometry
231 
232  this_object = self.thisMObject()
233 
234  plug = OpenMaya.MPlug(this_object, instanceShape.aRadius)
235  self.__myGeometry.radius = plug.asDouble()
236  plug = OpenMaya.MPlug(this_object, instanceShape.aCount)
237  self.__myGeometry.count = plug.asInt()
238 
239  plug = OpenMaya.MPlug(this_object, instanceShape.aInstanceShape)
240  plugArray = OpenMaya.MPlugArray()
241  plug.connectedTo( plugArray, True, False )
242  if ( plugArray.length() > 0 ):
243  node = plugArray[0].node()
244  dagNode = OpenMaya.MFnDagNode(node)
245  path = OpenMaya.MDagPath()
246  dagNode.getPath(path)
247  self.__myGeometry.instanceShape = path
248 
249  return self.__myGeometry
250 
251 #####################################################################
252 ##
253 ## UI class - defines the UI part of a shape node
254 ##
255 class instanceShapeUI(OpenMayaMPx.MPxSurfaceShapeUI):
256  # private enums
257  def __init__(self):
258  OpenMayaMPx.MPxSurfaceShapeUI.__init__(self)
259 
260  # override
261  def getDrawRequests(self, info, objectAndActiveOnly, queue):
262  """
263  The draw data is used to pass geometry through the
264  draw queue. The data should hold all the information
265  needed to draw the shape.
266  """
267  self.geometry = None
268 
269  # Custom instancer objects can instance other custom surface
270  # shapes. This is done by first getting the draw request
271  # data for the instancer shape
272  #
273  data = OpenMayaUI.MDrawData()
274  request = info.getPrototype(self)
275  shapeNode = self.surfaceShape()
276  path = info.multiPath()
277  view = info.view()
278  # We stored the instance object via a connection on the surface
279  # shape. Retrieve that value.
280  #
281  geom = shapeNode.geometry()
282  shadedMode = False
283  mainMaterial = None
284  if request.displayStyle() == OpenMayaUI.M3dView.kGouraudShaded:
285  shadedMode = True
286 
287  if geom.instanceShape:
288  # Find the MPxSurfaceShape for the object that we are instancing
289  #
290  shapeUI = OpenMayaMPx.MPxSurfaceShapeUI.surfaceShapeUI(geom.instanceShape)
291  if shapeUI:
292  mainMaterial = shapeUI.material( geom.instanceShape )
293  mainMaterial.evaluateMaterial( view, geom.instanceShape )
294  r = geom.radius
295  for a in range(geom.count):
296  myQueue = OpenMayaUI.MDrawRequestQueue()
297  percent = float(a)/float(geom.count)
298  rad = 2*math.pi * percent
299  position = (r*math.cos(rad), r*math.sin(rad),0.0)
300  # Construct a reference to MDrawInfo and modify it
301  # to point to the instance shape. If we do not do
302  # this in then the call to getDrawRequests will think
303  # that we are still the instancer shape and not the
304  # instance shape.
305  #
306  myinfo = OpenMayaUI.MDrawInfo( info )
307  myinfo.setMultiPath( geom.instanceShape )
308  shapeUI.getDrawRequests( myinfo,
309  objectAndActiveOnly, myQueue )
310  geom.drawQueueList.append( (myQueue, position) )
311 
312  info.setMultiPath( path )
313  # Finally we must supply a material back to the drawing code.
314  # We attempt to use the instance shape material; however, if
315  # that fails then we fall back to the default material
316  #
317  if shadedMode:
318  defaultMaterial = OpenMayaUI.MMaterial.defaultMaterial()
319  if not mainMaterial:
320  mainMaterial = defaultMaterial
321  try:
322  request.setMaterial( mainMaterial )
323  except:
324  request.setMaterial( defaultMaterial )
325 
326 
327  self.getDrawData(geom, data)
328  request.setDrawData(data)
329 
330  self.geometry = geom
331  queue.add(request)
332 
333  # override
334  def draw(self, request, view):
335  """
336  From the given draw request, get the draw data and determine
337  which basic shape to draw and with what values.
338  """
339 
340  data = request.drawData()
341  shapeNode = self.surfaceShape()
342  geom = self.geometry
343  glFT.glMatrixMode( OpenMayaRender.MGL_MODELVIEW )
344  if geom.instanceShape:
345  shapeUI = OpenMayaMPx.MPxSurfaceShapeUI.surfaceShapeUI(geom.instanceShape)
346  for (queue, pos) in geom.drawQueueList:
347  glFT.glPushMatrix();
348  glFT.glTranslatef( pos[0], pos[1], pos[2] )
349  while not queue.isEmpty():
350  request = queue.remove()
351  shapeUI.draw( request, view )
352  glFT.glPopMatrix()
353 
354  # Draw a shell area that shows where the instances are being
355  # drawn. This is nice to have if we don't have any instance
356  # shapes connected to this plugin.
357  #
358  glFT.glPushAttrib( OpenMayaRender.MGL_ALL_ATTRIB_BITS )
359  glFT.glPolygonMode(OpenMayaRender.MGL_FRONT_AND_BACK,
360  OpenMayaRender.MGL_LINE)
361  glFT.glBegin(OpenMayaRender.MGL_QUADS)
362  glFT.glVertex3f(-1*(geom.radius), -1*(geom.radius), 0.0)
363  glFT.glNormal3f(0, 0, 1.0)
364 
365  glFT.glVertex3f(-1*(geom.radius), (geom.radius), 0.0)
366  glFT.glNormal3f(0, 0, 1.0)
367 
368  glFT.glVertex3f((geom.radius), (geom.radius), 0.0)
369  glFT.glNormal3f(0, 0, 1.0)
370 
371  glFT.glVertex3f((geom.radius), -1*(geom.radius), 0.0)
372  glFT.glNormal3f(0, 0, 1.0)
373  glFT.glEnd()
374  glFT.glPopAttrib( )
375 
376  # override
377  def select(self, selectInfo, selectionList, worldSpaceSelectPts):
378  """
379  Select function. Gets called when the bbox for the object is
380  selected. This function just selects the object without
381  doing any intersection tests.
382  """
383 
384  priorityMask = OpenMaya.MSelectionMask(OpenMaya.MSelectionMask.kSelectObjectsMask)
385  item = OpenMaya.MSelectionList()
386  item.add(selectInfo.selectPath())
387  xformedPt = OpenMaya.MPoint()
388  selectInfo.addSelection(item, xformedPt, selectionList,
389  worldSpaceSelectPts, priorityMask, False)
390  return True
391 
392 
393 
394 
395 #####################################################################
396 
397 def nodeCreator():
398  return OpenMayaMPx.asMPxPtr( instanceShape() )
399 
400 
401 def uiCreator():
402  return OpenMayaMPx.asMPxPtr( instanceShapeUI() )
403 
404 
405 def nodeInitializer():
406  # utility func for numeric attrs
407  def setOptions(attr):
408  attr.setHidden(False)
409  attr.setKeyable(True)
410  attr.setInternal(True)
411 
412  messageAttr = OpenMaya.MFnMessageAttribute()
413  numericAttr = OpenMaya.MFnNumericAttribute()
414 
415  instanceShape.aInstanceShape = messageAttr.create("instanceShape", "is")
416  instanceShape.addAttribute(instanceShape.aInstanceShape)
417 
418  instanceShape.aRadius = numericAttr.create("radius", "r", OpenMaya.MFnNumericData.kDouble, kDefaultRadius)
419  setOptions(numericAttr)
420  instanceShape.addAttribute(instanceShape.aRadius)
421 
422  instanceShape.aCount = numericAttr.create("count", "ct", OpenMaya.MFnNumericData.kInt, kDefaultCount)
423  setOptions(numericAttr)
424  instanceShape.addAttribute(instanceShape.aCount)
425 
426 # initialize the script plug-in
427 def initializePlugin(mobject):
428  mplugin = OpenMayaMPx.MFnPlugin(mobject, "Autodesk", "2011", "Any")
429  try:
430  mplugin.registerShape( kPluginNodeTypeName, spInstanceShapeNodeId,
431  nodeCreator, nodeInitializer, uiCreator )
432  except:
433  sys.stderr.write( "Failed to register node: %s" % kPluginNodeTypeName )
434  raise
435 
436 
437 # uninitialize the script plug-in
438 def uninitializePlugin(mobject):
439  mplugin = OpenMayaMPx.MFnPlugin(mobject)
440  try:
441  mplugin.deregisterNode( spInstanceShapeNodeId )
442  except:
443  sys.stderr.write( "Failed to deregister node: %s" % kPluginNodeTypeName )
444  raise