scripted/geometryOverrideHighPerformance.py

scripted/geometryOverrideHighPerformance.py
1 ##-
2 ## ==========================================================================
3 ## Copyright 2018 Autodesk, Inc. All rights reserved.
4 ##
5 ## Use of this software is subject to the terms of the Autodesk
6 ## license agreement provided at the time of installation or download,
7 ## or which otherwise accompanies this software in either electronic
8 ## or hard copy form.
9 ## ==========================================================================
10 ##+
11 
12 ###################################################################################################
13 ##
14 ## This plug-in shows how to use MPxGeometryOverride to render geometry with the best performance
15 ## as possible. It will register a new type of node "geometryOverrideHighPerformance_shape_py" and associate it to a
16 ## MPxGeometryOverride.
17 ##
18 ## The GeometryOverrideHighPerformance_shape class derives from MPxSurfaceShape and add two attributes to the node.
19 ## The first attribute is the size of the shape that will be used by the geometry override to scale
20 ## the vertex position data. The second attribute is a input mesh that will be use by the geometry
21 ## override to render. The GeometryOverrideHighPerformance_shape also enable the shape to be selectable in
22 ## Viewport 2.0.
23 ##
24 ## The GeometryOverrideHighPerformance class derives from MPxGeometryOverride and shows how to
25 ## create / update different render items and fill vertex / index buffers to render the geometry
26 ## of performanceExample_shape. This class try to be smart as possible to only update the geometry
27 ## data as needed.
28 ##
29 ## This plugin uses two different techniques to optimize the performance of the GeometryOverrideHighPerformance_shape
30 ## draw in VP2.
31 ##
32 ## Technique 1:
33 ## In order to know when the GeometryOverrideHighPerformance render items need to be updated
34 ## the factors affecting how render items are drawn are stored. When updateRenderItems()
35 ## is called the current values can be compared against the previously used values,
36 ## allowing Maya to call updateRenderItems() only when necessary.
37 ##
38 ## Technique 2:
39 ## In order to know when the GeometryOverrideHighPerformance geometry needs to be updated the
40 ## GeometryOverrideHighPerformance_shape node look into dirty propagation and the evaluation manager
41 ## to track when attributes which affect geometry change. GeometryOverrideHighPerformance can then query
42 ## GeometryOverrideHighPerformance_shape to find out if a geometry update is necessary.
43 ##
44 ## Evaluation Manager Parallel Update:
45 ## This plugin enable the Evaluation Manager Parallel Update by returning true from
46 ## supportsEvaluationManagerParallelUpdate(). Evaluation Manager Parallel Update is a
47 ## performance optimization which allows the updateDG() and populateGeometry() step
48 ## of the draw preparation to occur during Evaluation Graph Execution rather than during
49 ## VP2 refresh. That's means the Maya nodes begin preparing render data for
50 ## VP2 immediately after a node is evaluated, and before the rest of the graph has
51 ## finished evaluation. This improves performance by allowing updateDG() and
52 ## populateGeometry() calls of several plug-ins to run in parallel with each other
53 ## and with Evaluation Manager dependency graph evaluation.
54 ##
55 ## Evaluation Caching:
56 ## GeometryOverrideHighPerformance is fully compatible with Evaluation Caching. Supporting Evaluation Caching
57 ## adds some additional, subtle requirements on the plug-in node. Evaluation Caching
58 ## automatically caches data for two types of attributes: output attributes and dynamic
59 ## attributes, where output attributes are defined as any attribute which is affected by
60 ## another attribute on the node. The affects relationship can be created either by
61 ## calling MPxNode::attributeAffects() or by returning affected attributes from
62 ## MPxNode::setDependentsDirty().
63 ##
64 ## By default, Maya doesn't add a custom shape (MPxSurfaceShape) into the cache system.
65 ## So, that means we need to to add the node type into the system ourself with mel
66 ## command. See performanceExample.mel.
67 ##
68 ## Using Evaluation Caching with Evaluation Manager Parallel Update has an additional issue
69 ## to be aware of. When using Evaluation Manager Parallel Update some MPxGeometryOverride
70 ## methods are called after the corresponding DAG node has been evaluated but before the
71 ## full evaluation graph has been evaluated. When Evaluation Caching is enabled only
72 ## cached DG values may be read before the full evaluation graph has been evaluated. Therefore,
73 ## when using Evaluation Manager Parallel Update and Evaluation Caching only cached DG values
74 ## may be read. Attempting to read an uncached value before evaluation finishes results in
75 ## undefined behavior (incorrect data or crashes).
76 ##
77 ## VP2 Custom Caching:
78 ## GeometryOverrideHighPerformance is fully compatible with VP2 Custom Caching. When using VP2
79 ## custom caching the MPxGeometryOverride may be invoked in the normal context or in a
80 ## background thread using another context. If playback and background evaluation occur
81 ## concurrently Maya guarantees that all of the MPxGeoemtryOverride methods called for a
82 ## context occur atomically without being interleaved with MPxGeometryOverride methods
83 ## for the same DAG object in a different context.
84 ##
85 ##
86 ##
87 ## To use this plug-in run the following mel or python commands once the plugin is loaded
88 ##
89 ## Mel script:
90 ##
91 ## //
92 ## // Add the geometryOverrideHighPerformance_shape_py node type in evaluation caching
93 ## //
94 ## cacheEvaluator -newFilter "nodeTypes"
95 ## -newFilterParam "types=+geometryOverrideHighPerformance_shape_py"
96 ## -newAction "enableEvaluationCache";
97 ##
98 ## //
99 ## // Add geometryOverrideHighPerformance_shape_py node type in VP2 SW Cache
100 ## //
101 ## cacheEvaluator -newFilter "nodeTypes"
102 ## -newFilterParam "types=+geometryOverrideHighPerformance_shape_py"
103 ## -newAction "enableVP2Cache"
104 ## -newActionParam "useHardware=0";
105 ##
106 ## //
107 ## // Add geometryOverrideHighPerformance_shape_py node type in VP2 HW Cache
108 ## //
109 ## cacheEvaluator -newFilter "nodeTypes"
110 ## -newFilterParam "types=+geometryOverrideHighPerformance_shape_py"
111 ## -newAction "enableVP2Cache"
112 ## -newActionParam "useHardware=1";
113 ##
114 ## $shapeNodeName = `createNode geometryOverrideHighPerformance_shape_py`;
115 ## sets -add initialShadingGroup $shapeNodeName;
116 ##
117 ## Python script:
118 ##
119 ## maya.cmds.cacheEvaluator(newFilter="nodeTypes", newFilterParam = "types=+geometryOverrideHighPerformance_shape_py" , newAction = "enableEvaluationCache")
120 ## maya.cmds.cacheEvaluator(newFilter="nodeTypes", newFilterParam = "types=+geometryOverrideHighPerformance_shape_py" , newAction = "enableVP2Cache", newActionParam = "useHardware=0")
121 ## maya.cmds.cacheEvaluator(newFilter="nodeTypes", newFilterParam = "types=+geometryOverrideHighPerformance_shape_py" , newAction = "enableVP2Cache", newActionParam = "useHardware=1")
122 ##
123 ## shapeNodeName = maya.cmds.createNode('geometryOverrideHighPerformance_shape_py')
124 ## maya.cmds.sets(shapeNodeName, add = 'initialShadingGroup')
125 ##
126 ## Or run the performanceExample.mel
127 ##
128 ## Note : the performanceExample.mel is written to use the c++ version of the plug-in.
129 ## The only difference between the c++ version and the python version of this
130 ## plug-in is the node type name (performanceExample_shape in c++ and
131 ## geometryOverrideHighPerformance_shape_py in python). So to use the performanceExample.mel
132 ## you need to replace all "performanceExample_shape" for "geometryOverrideHighPerformance_shape_py"
133 ##
134 ##
135 ## Warning : This example show how to use MPxGeometryOverride to get the best performance as possible but
136 ## in some situations it can be very slow with Python. By example, having to much node can be very slow
137 ## caused by limitation of Python integration in Maya. Maya execute all python nodes in serial even
138 ## if evaluation mode is parallel. Also, MPxGeometryOverride.populateGeometry() can be very slow if
139 ## the vertex buffers need to be filled with a huge amount of vertices. This can have a impact on node
140 ## selection, during background evaluation or during playback.
141 ##
142 ##
143 ##
144 ###################################################################################################
145 import ctypes
146 import sys
147 import math
148 import maya.api.OpenMaya as om
149 import maya.api.OpenMayaUI as omui
150 import maya.api.OpenMayaRender as omr
151 
152 
153 class GeometryOverrideHighPerformance_shape(om.MPxSurfaceShape):
154  id = om.MTypeId(0x80040)
155  fsInputMesh = None ## The input mesh to use (Node attribute)
156  fsInputMeshBoundingBoxMin = None
157  fsInputMeshBoundingBoxMax = None
158  fsSize = None ## The size of the cube (Node attribute)
159  fsSizeChangedSinceVP2Update = None
160  fsInputMeshChanged = None
161  fsInputMeshGeoChanged = None
162  fsMaterialChanged = None
163 
164  # Default mesh data used if there is no input mesh connected
165  fsPositions = []
166  fsNormals = []
167  fsTangents = []
168  fsBiTangents= []
169  fsTexCoords = []
170  fsIndices = []
171  fsBoundingBox = None
172 
173  @staticmethod
174  def creator():
175  return GeometryOverrideHighPerformance_shape()
176 
177  @staticmethod
178  def instancingChangedCallback(childPath, parentPath, clientData):
179  om.MGlobal.displayWarning("GeometryOverrideHighPerformance does't support instancing !")
180 
181  @staticmethod
182  def initialize():
183  unitFn = om.MFnUnitAttribute()
184 
185  ## ----------------------- INPUTS --------------------------
186 
187  ##
188  ## Create the size attribute
189  ##
190  GeometryOverrideHighPerformance_shape.fsSize = unitFn.create("size", "sz", om.MFnUnitAttribute.kDistance)
191  unitFn.default = om.MDistance(1.0)
192  unitFn.keyable = True
193  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsSize)
194 
195 
196  ##
197  ## Create the input mesh attribute
198  ##
199  typedFn = om.MFnTypedAttribute()
200  GeometryOverrideHighPerformance_shape.fsInputMesh = typedFn.create("inMesh", "i", om.MFnData.kMesh, om.MObject.kNullObj)
201  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsInputMesh)
202 
203 
204  ##
205  ## bbox attributes
206  ##
207  numericAttributeFn = om.MFnNumericAttribute()
208  GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin = numericAttributeFn.create("inputBoundMin", "inbn", om.MFnNumericData.k3Double, 0)
209  numericAttributeFn.array = False
210  numericAttributeFn.usesArrayDataBuilder = False
211  numericAttributeFn.hidden = False
212  numericAttributeFn.keyable = True
213  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin)
214 
215  GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax = numericAttributeFn.create("inputBoundMax", "inbm", om.MFnNumericData.k3Double, 0)
216  numericAttributeFn.array = False
217  numericAttributeFn.usesArrayDataBuilder = False
218  numericAttributeFn.hidden = False
219  numericAttributeFn.keyable = True
220  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax)
221 
222  om.MPxNode.attributeAffects(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin, om.MPxSurfaceShape.nodeBoundingBoxMin)
223  om.MPxNode.attributeAffects(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax, om.MPxSurfaceShape.nodeBoundingBoxMax)
224  om.MPxNode.attributeAffects(GeometryOverrideHighPerformance_shape.fsSize, om.MPxSurfaceShape.nodeBoundingBoxMin)
225  om.MPxNode.attributeAffects(GeometryOverrideHighPerformance_shape.fsSize, om.MPxSurfaceShape.nodeBoundingBoxMax)
226 
227  ## VP2 Custom Caching: When using VP2 custom caching the MPxGeometryOverride associated
228  ## with the node might be invoked in the normal context or in the background
229  ## context. If playback and background evaluation occur concurrently Maya guarantees
230  ## that all of the MPxGeometryOverride methods called for a context occur atomically
231  ## without being interleaved with MPxGeometryOverride methods for the same object in
232  ## a different context.
233  ##
234  ## However, Maya does not make any timing guarantee between the call(s) to evaluate the
235  ## MPxNode and calls to the MPxGeometryOverride. For example, a postEvaluation call
236  ## in the background evaluation thread may occur at the same time that the foreground
237  ## thread is using the MPxGeometryOverride.
238  ##
239  ## This means that any communication which occurs between the MPxNode during evaluation
240  ## and the MPxGeometryOverride must be context aware. The communication channel must
241  ## use different storage for each context. The easiest way to implement this to use
242  ## internal attributes on the MPxNode that the MPxGeometryOverride has access to.
243  ##
244  ## Don't create any affects relationships because those attribute doesn't use any
245  ## Maya dirty management or evaluation. They are used only by getting the MDataHandle
246  ## directly from the MDataBlock using outputValue().
247  attrFn = om.MFnNumericAttribute()
248  GeometryOverrideHighPerformance_shape.fsSizeChangedSinceVP2Update = attrFn.create("sizeChangedSinceVP2Update", "sd", om.MFnNumericData.kBoolean, True)
249  attrFn.storable = False
250  attrFn.hidden = True
251  attrFn.connectable = False
252  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsSizeChangedSinceVP2Update)
253 
254  attrFn = om.MFnNumericAttribute()
255  GeometryOverrideHighPerformance_shape.fsInputMeshChanged = attrFn.create("inputMeshChangedSinceVP2Update", "imd", om.MFnNumericData.kBoolean, True)
256  attrFn.storable = False
257  attrFn.hidden = True
258  attrFn.connectable = False
259  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsInputMeshChanged)
260 
261  attrFn = om.MFnNumericAttribute()
262  GeometryOverrideHighPerformance_shape.fsInputMeshGeoChanged = attrFn.create("inputMeshGeoChangedSinceVP2Update", "img", om.MFnNumericData.kBoolean, True)
263  attrFn.storable = False
264  attrFn.hidden = True
265  attrFn.connectable = False
266  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsInputMeshGeoChanged)
267 
268  attrFn = om.MFnNumericAttribute()
269  GeometryOverrideHighPerformance_shape.fsMaterialChanged = attrFn.create("materialChangedSinceVP2Update", "mcd", om.MFnNumericData.kBoolean, True)
270  attrFn.storable = False
271  attrFn.hidden = True
272  attrFn.connectable = False
273  om.MPxNode.addAttribute(GeometryOverrideHighPerformance_shape.fsMaterialChanged)
274 
275  GeometryOverrideHighPerformance_shape.createSphere(1, 50, 50)
276 
277  def __init__(self):
278  om.MPxSurfaceShape.__init__(self)
279 
280  self.fUseInputMesh = False
281 
282  self.mInstanceAddedCallbackId = None
283 
284  def __del__(self):
285  om.MMessage.removeCallback(self.mInstanceAddedCallbackId)
286 
287  def postConstructor(self):
288  """
289  When instances of this node are created internally, the MObject
290  associated with the instance is not created until after the constructor
291  of this class is called. This means that no member functions of
292  MPxSurfaceShape can be called in the constructor. The postConstructor
293  solves this problem. Maya will call this function after the internal
294  object has been created. As a general rule do all of your initialization
295  in the postConstructor.
296 
297  Note : For more information, see MPxSurfaceShape.postConstructor()
298  """
299 
300  ## This call allows the shape to have shading groups assigned
301  self.isRenderable = True
302 
303  ##
304  ## Add a callback that will be called when instance are added.
305  ##
306  path = om.MDagPath.getAPathTo(self.thisMObject())
307  self.mInstanceAddedCallbackId = om.MDagMessage.addInstanceAddedDagPathCallback(path, GeometryOverrideHighPerformance_shape.instancingChangedCallback)
308 
309  def getShapeSelectionMask(self):
310  """
311  This method is overriden to support interactive object selection in
312  Viewport 2.0
313 
314  Returns The selection mask of the shaper.
315 
316  Note : For more information, see MPxSurfaceShape::getShapeSelectionMask()
317  """
318 
319  selType = om.MSelectionMask.kSelectMeshes
320  return om.MSelectionMask( selType )
321 
322  def isBounded(self):
323  return True
324 
325  def boundingBox(self):
326  """
327  Returns the bounding box for this object.
328  It is a good idea not to recompute here as this function is called
329  often.
330 
331  Note : This function is only called if MPxSurfaceShape.isBounded()
332  return true. For more information, see MPxSurfaceShape.boundingBox()
333  """
334  thisNode = self.thisMObject()
335 
336  ##
337  ## Get the value of the size attribute
338  ##
339  plug = om.MPlug( thisNode, GeometryOverrideHighPerformance_shape.fsSize )
340  sizeVal = plug.asMDistance()
341  multiplier = sizeVal.asCentimeters()
342 
343  ##
344  ## Return the scaled input mesh bounding box if there is a input
345  ## mesh connected to the node
346  ##
347  if self.fUseInputMesh:
348  block = self.forceCache()
349  boundingBoxMin = block.inputValue(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin).asDouble3()
350  boundingBoxMax = block.inputValue(GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax).asDouble3()
351  return om.MBoundingBox(om.MPoint(boundingBoxMin) * multiplier, om.MPoint(boundingBoxMax) * multiplier)
352 
353  ##
354  ## Return the scaled hardcoded mesh bounding box
355  ##
356  return om.MBoundingBox(GeometryOverrideHighPerformance_shape.fsBoundingBox.min * multiplier, GeometryOverrideHighPerformance_shape.fsBoundingBox.max * multiplier)
357 
358  def connectionMade(self, plug, otherPlug, asSrc):
359  """
360  This method gets called when connections are made to attributes
361  of this node.
362 
363  Note : For more information, see MPxNode.connectionMade()
364  """
365  ## If the plug get connected with a shading engine, set fMaterialChanged to true,
366  ## so in the geometry override updateRenderItems() we can return true to force
367  ## the render items to be updated.
368  if (asSrc and
369  otherPlug.node().hasFn(om.MFn.kShadingEngine) and
370  (plug.attribute() == om.MPxSurfaceShape.instObjGroups or
371  plug.attribute() == om.MPxSurfaceShape.objectGroups)):
372 
373  self.setMaterialChangedSinceVP2Update(True)
374 
375  ## When a input mesh is connected to the node, we must also connect the input mesh
376  ## bounding box attribute to this node to be able to get easly the mesh bounding box.
377  if (not asSrc and
378  otherPlug.node().hasFn(om.MFn.kMesh) and
379  plug.attribute() == GeometryOverrideHighPerformance_shape.fsInputMesh):
380 
381  self.fUseInputMesh = True
382  self.setInputMeshChangedSinceVP2Update(True)
383 
384  thisNode = self.thisMObject()
385  boundingBoxMinPlug = om.MPlug(thisNode, GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin)
386  boundingBoxMaxPlug = om.MPlug(thisNode, GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax)
387 
388  inputMeshBoundingBoxMinPlug = om.MPlug(otherPlug.node(), om.MPxSurfaceShape.nodeBoundingBoxMin)
389  inputMeshBoundingBoxMaxPlug = om.MPlug(otherPlug.node(), om.MPxSurfaceShape.nodeBoundingBoxMax)
390 
391  modifier = om.MDGModifier()
392  modifier.connect(inputMeshBoundingBoxMinPlug, boundingBoxMinPlug)
393  modifier.connect(inputMeshBoundingBoxMaxPlug, boundingBoxMaxPlug)
394  modifier.doIt()
395 
396  return om.MPxNode.connectionMade(self, plug, otherPlug, asSrc)
397 
398  def connectionBroken(self, plug, otherPlug, asSrc):
399  """
400  This method gets called when connections are broken with attributes
401  of this node.
402 
403  Note : For more information, see MPxNode.connectionBroken()
404  """
405  ## If the plug get disconnected with a shading engine, set fMaterialChanged to true,
406  ## so in the geometry override updateRenderItems() we can return true to force
407  ## the render items to be updated.
408  if (asSrc and
409  otherPlug.node().hasFn(om.MFn.kShadingEngine) and
410  (plug.attribute() == om.MPxSurfaceShape.instObjGroups or
411  plug.attribute() == om.MPxSurfaceShape.objectGroups)):
412 
413  self.fMaterialChanged = True
414 
415  ## When a input mesh is disconnected from the node,
416  ## we also must disconnect the input mesh bounding box attribute from this node
417  if (not asSrc and
418  otherPlug.node().hasFn(om.MFn.kMesh) and
419  plug.attribute() == GeometryOverrideHighPerformance_shape.fsInputMesh):
420 
421  self.fUseInputMesh = False
422  self.setInputMeshChangedSinceVP2Update(True)
423 
424  thisNode = self.thisMObject()
425  boundingBoxMinPlug = om.MPlug(thisNode, GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMin)
426  boundingBoxMaxPlug = om.MPlug(thisNode, GeometryOverrideHighPerformance_shape.fsInputMeshBoundingBoxMax)
427 
428  inputMeshBoundingBoxMinPlug = om.MPlug(otherPlug.node(), om.MPxSurfaceShape.nodeBoundingBoxMin)
429  inputMeshBoundingBoxMaxPlug = om.MPlug(otherPlug.node(), om.MPxSurfaceShape.nodeBoundingBoxMax)
430 
431  modifier = om.MDGModifier()
432  modifier.disconnect(inputMeshBoundingBoxMinPlug, boundingBoxMinPlug)
433  modifier.disconnect(inputMeshBoundingBoxMaxPlug, boundingBoxMaxPlug)
434  modifier.doIt()
435 
436  return om.MPxNode.connectionBroken(self, plug, otherPlug, asSrc)
437 
438  def setDependentsDirty(self, plug, plugArray):
439  """
440  This method can be overridden in user defined nodes to specify
441  which plugs should be set dirty based upon an input plug
442 
443  Note : For more information, see MPxNode.setDependentsDirty()
444  """
445  if (plug.attribute() == GeometryOverrideHighPerformance_shape.fsSize):
446 
447  self.setSizeChangedSinceVP2Update(True)
448 
449  elif(plug.attribute() == GeometryOverrideHighPerformance_shape.fsInputMesh):
450  self.setInputMeshGeoChangedSinceVP2Update(True)
451 
452  def postEvaluation(self, context, evaluationNode, evalType):
453  """
454  After the evaluation graph execution, each node gets a chance to
455  restore / update its internal states.For example, resetting
456  draw state.
457 
458  This code has to be thread safe, non - blocking and work only on
459  data owned by the node.
460 
461  This call will most likely happen from a worker thread.
462 
463  Note : For more information, see MPxNode.postEvaluation()
464  """
465  if evaluationNode.dirtyPlugExists(GeometryOverrideHighPerformance_shape.fsSize):
466 
467  self.setSizeChangedSinceVP2Update(True)
468 
469  if evaluationNode.dirtyPlugExists(GeometryOverrideHighPerformance_shape.fsInputMesh):
470 
471  self.setInputMeshGeoChangedSinceVP2Update(True)
472 
473 
474  def getPositions(self):
475  return GeometryOverrideHighPerformance_shape.fsPositions
476 
477  def getNormals(self):
478  return GeometryOverrideHighPerformance_shape.fsNormals
479 
480  def getTangents(self):
481  return GeometryOverrideHighPerformance_shape.fsTangents
482 
483  def getBiTangents(self):
484  return GeometryOverrideHighPerformance_shape.fsBiTangents
485 
486  def getTextureCoords(self):
487  return GeometryOverrideHighPerformance_shape.fsTexCoords
488 
489  def getIndices(self):
490  return GeometryOverrideHighPerformance_shape.fsIndices
491 
492  def setSizeChangedSinceVP2Update(self, value):
493  ##
494  ## Calling forceCache here should be fast. Possible calling sites are:
495  ## - setDependentsDirty() -> the normal context is current.
496  ## - preparing the draw in VP2 -> the normal context is current.
497  ## - background evaluation postEvaluation() -> datablock for background context already exists.
498  ## - background evaluation for VP2 Custom Caching -> datablock for background context already exists.
499  ##
500  dataBlock = self.forceCache()
501  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsSizeChangedSinceVP2Update)
502  dataHandle.setBool(value)
503 
504  def evalSizeChangedSinceVP2Update(self):
505  dataBlock = self.forceCache()
506  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsSizeChangedSinceVP2Update)
507  return dataHandle.asBool()
508 
509  def evalInputMeshChangedSinceVP2Update(self):
510  dataBlock = self.forceCache()
511  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsInputMeshChanged)
512  return dataHandle.asBool()
513 
514  def setInputMeshChangedSinceVP2Update(self, value):
515  dataBlock = self.forceCache()
516  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsInputMeshChanged)
517  dataHandle.setBool(value)
518 
519  def evalInputMeshGeoChangedSinceVP2Update(self):
520  dataBlock = self.forceCache()
521  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsInputMeshGeoChanged)
522  return dataHandle.asBool()
523 
524  def setInputMeshGeoChangedSinceVP2Update(self, value):
525  dataBlock = self.forceCache()
526  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsInputMeshGeoChanged)
527  dataHandle.setBool(value)
528 
529  def evalMaterialChangedSinceVP2Update(self):
530  dataBlock = self.forceCache()
531  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsMaterialChanged)
532  return dataHandle.asBool()
533 
534  def setMaterialChangedSinceVP2Update(self, value):
535  dataBlock = self.forceCache()
536  dataHandle = dataBlock.outputValue(GeometryOverrideHighPerformance_shape.fsMaterialChanged)
537  dataHandle.setBool(value)
538 
539  @staticmethod
540  def createSphere(radius, sliceCount, stackCount):
541 
542  ## vertex on the top of the sphere
543  GeometryOverrideHighPerformance_shape.fsPositions.append([0.0, radius, 0.0] )
544  GeometryOverrideHighPerformance_shape.fsNormals.append([0.0, 1.0, 0.0])
545  GeometryOverrideHighPerformance_shape.fsTangents.append([1.0, 0.0, 0.0])
546  GeometryOverrideHighPerformance_shape.fsBiTangents.append([0.0, 0.0, -1.0])
547  GeometryOverrideHighPerformance_shape.fsTexCoords.append([0.0, 0.0])
548 
549 
550  phiStep = math.pi / stackCount
551  thetaStep = 2.0 * math.pi / sliceCount
552 
553 
554  ## Compute vertices for each stack ring
555  for i in xrange(1, stackCount):
556  phi = i * phiStep
557 
558  ## Vertices of ring.
559  for j in xrange(0, sliceCount+1):
560  theta = j * thetaStep
561 
562  ## spherical to cartesian
563  positionX = radius * math.sin(phi) * math.cos(theta)
564  positionY = radius * math.cos(phi)
565  positionZ = radius * math.sin(phi) * math.sin(theta)
566  GeometryOverrideHighPerformance_shape.fsPositions.append([positionX, positionY, positionZ])
567 
568  normal = om.MFloatVector(positionX , positionY , positionZ)
569  normal.normalize()
570  GeometryOverrideHighPerformance_shape.fsNormals.append([normal.x, normal.y, normal.z])
571 
572  ## Partial derivative of P with respect to theta
573  tangent = om.MFloatVector( -radius * math.sin(phi) * math.sin(theta),
574  0.0,
575  radius * math.sin(phi) * math.cos(theta) )
576  tangent.normalize()
577  GeometryOverrideHighPerformance_shape.fsTangents.append([tangent.x, tangent.y, tangent.z])
578 
579  biTangents = normal ^ tangent
580  GeometryOverrideHighPerformance_shape.fsBiTangents.append(biTangents)
581 
582  GeometryOverrideHighPerformance_shape.fsTexCoords.append([ theta / math.pi, phi / math.pi])
583 
584  ## vertex at the buttom of the sphere
585  GeometryOverrideHighPerformance_shape.fsPositions.append([0.0, -radius, 0.0])
586  GeometryOverrideHighPerformance_shape.fsNormals.append([0.0, -1.0, 0.0])
587  GeometryOverrideHighPerformance_shape.fsTangents.append([1.0, 0.0, 0.0])
588  GeometryOverrideHighPerformance_shape.fsBiTangents.append([0.0, 0.0, -1.0])
589  GeometryOverrideHighPerformance_shape.fsTexCoords.append([0.0, 1.0])
590 
591  ##
592  ## Compute indices for top stack. The top stack was written first to the vertex buffer
593  ## and connects the top pole to the first ring.
594  ##
595 
596  for i in xrange(1,sliceCount+1):
597  GeometryOverrideHighPerformance_shape.fsIndices.append(0)
598  GeometryOverrideHighPerformance_shape.fsIndices.append(i + 1)
599  GeometryOverrideHighPerformance_shape.fsIndices.append(i)
600 
601  ## Offset the indices to the index of the first vertex in the first ring.
602  ## This is just skipping the top pole vertex.
603  baseIndex = 1
604  ringVertexCount = sliceCount + 1
605  for i in xrange(sliceCount - 2):
606  for j in xrange(sliceCount):
607  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + i*ringVertexCount + j)
608  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + i*ringVertexCount + j + 1)
609  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + (i + 1)*ringVertexCount + j)
610 
611  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + (i + 1)*ringVertexCount + j)
612  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + i*ringVertexCount + j + 1)
613  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + (i + 1)*ringVertexCount + j + 1)
614 
615  ## South pole vertex was added last.
616  southPoleIndex = len(GeometryOverrideHighPerformance_shape.fsPositions) - 1
617 
618  ## Offset the indices to the index of the first vertex in the last ring.
619  baseIndex = southPoleIndex - ringVertexCount;
620 
621  for i in xrange(sliceCount):
622  GeometryOverrideHighPerformance_shape.fsIndices.append(southPoleIndex)
623  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + i)
624  GeometryOverrideHighPerformance_shape.fsIndices.append(baseIndex + i + 1)
625 
626 
627  ##
628  ## Compute the bounding box
629  ##
630  corner1Point = om.MPoint(-radius, -radius, -radius)
631  corner2Point = om.MPoint(radius, radius, radius)
632  GeometryOverrideHighPerformance_shape.fsBoundingBox = om.MBoundingBox(corner1Point, corner2Point)
633 
634 
635 #################################################################################
636 ##
637 ## PerformanceExampleGeometryOverride implementation
638 ##
639 #################################################################################
640 
641 class PerformanceExampleGeometryOverride(omr.MPxGeometryOverride):
642  sActiveWireframeRenderItemName = "PerformanceExampleGeometryOverride_ActiveWireframe"
643  sDormantWireframeRenderItemName= "PerformanceExampleGeometryOverride_DormantWireframe"
644  sCustomShadedItemName = "PerformanceExampleGeometryOverride_shaded"
645 
646  @staticmethod
647  def creator(obj):
648  return PerformanceExampleGeometryOverride(obj)
649 
650  def __init__(self, obj):
651  omr.MPxGeometryOverride.__init__(self, obj)
652 
653  node = om.MFnDependencyNode(obj)
654  self.fMesh = node.userNode()
655 
656  self.fSurfaceNode = obj
657  self.fInputMeshPath = None
658 
659  self.fDisplayStatus = None
660  self.fWireframeDisplayColor = None
661 
662  self.fSizeMultiplier = 1.0
663 
664  def __del__(self):
665  self.fMesh = None
666 
667  def supportedDrawAPIs(self):
668  """
669  This function return draw API that is supported by this plugin.
670 
671  Note : Dependency graph evaluation is not allowed in this function.
672  Note : For more information, see MPxGeometryOverride.supportedDrawAPIs()
673  """
674 
675  ## this plugin supports both GL and DX
676  return omr.MRenderer.kOpenGL | omr.MRenderer.kDirectX11 | omr.MRenderer.kOpenGLCoreProfile
677 
678  def updateDG(self):
679  """
680  Perform any work required to translate the geometry data that needs to get
681  information from the dependency graph.This should be the only place that
682  dependency graph evaluation occurs.Any data retrieved should be cached for
683  later stages.
684 
685  Note : For more information, see MPxGeometryOverride.updateDG()
686  """
687 
688  ##
689  ## Cache the input mesh dagpath if the input mesh has changed
690  ##
691  if self.fMesh and self.fMesh.evalInputMeshChangedSinceVP2Update():
692 
693  plug = om.MPlug(self.fSurfaceNode, GeometryOverrideHighPerformance_shape.fsInputMesh)
694 
695  self.fInputMeshPath = None
696 
697  connections = plug.connectedTo(True, False)
698 
699  for i in xrange(len(connections)):
700  node = connections[i].node()
701 
702  if node.hasFn(om.MFn.kMesh):
703  self.fInputMeshPath = om.MDagPath.getAPathTo(node)
704  break
705 
706  ## Only update fSizeMultiplier when the footPrint node has signaled that mMultiplier
707  ## has changed.
708  if self.fMesh and self.fMesh.evalSizeChangedSinceVP2Update():
709  plug = om.MPlug(self.fSurfaceNode, GeometryOverrideHighPerformance_shape.fsSize)
710 
711  if plug:
712  sizeVal = plug.asMDistance()
713  self.fSizeMultiplier = sizeVal.asCentimeters()
714 
715  def isStreamDirty(self, desc):
716  """
717  This method is called for each geometry stream on the assocated DAG object
718  whenever the object changes.This method is passed a vertex buffer
719  descriptor representing one stream on the object to be updated.This
720  method should return false if it is safe to reuse the existing buffer rather
721  than filling a new buffer with data. Note that returning false from
722  isStreamDirty may NOT prevent populateGeometry from requiring that a stream
723  be updated.
724 
725  Note : Dependency graph evaluation is not allowed in this function.
726  Note : For more information, see MPxGeometryOverride.isStreamDirty()
727  """
728  if self.fInputMeshPath:
729  if self.fMesh and self.fMesh.evalInputMeshChangedSinceVP2Update():
730 
731  ## The input mesh changed since the last VP2 update (populate geometry).
732  ## That means during the last VP2 update we were using the default mesh
733  ## or another input mesh so, make all the stream dirty to be able to
734  ## populate them with the default mesh data.
735  return True
736  elif self.fMesh and \
737  (self.fMesh.evalInputMeshGeoChangedSinceVP2Update() or self.fMesh.evalSizeChangedSinceVP2Update()):
738 
739  ## When the input mesh geometry change or when the size attribute change,
740  ## we need to mark all the streams as dirty because MGeometryExtractor is used
741  ## to get the input mesh data and MGeometryExtractor return different result
742  ## depending on stream combination (Number of vertex for each stream).
743  return True
744  else:
745  return True
746  else:
747  if(self.fMesh and self.fMesh.evalInputMeshChangedSinceVP2Update()):
748 
749  ## The input mesh changed since the last VP2 update (populate geometry).
750  ## That means during the last VP2 update we were using a input mesh that
751  ## have been disconnected so, make all the stream dirty to be able to
752  ## populate them with the default mesh data.
753  return True
754 
755  elif desc.semantic == omr.MGeometry.kPosition:
756 
757  ## We only need to make the position stream dirty if the size attribute changed
758  return self.fMesh and self.fMesh.evalSizeChangedSinceVP2Update()
759 
760  else:
761 
762  ## For the default mesh, we assume that all the others stream (Normal, Tangent ...)
763  ## does't change so mark them all as not dirty.
764  return False
765 
766  return True
767 
768  def isIndexingDirty(self, item):
769  """
770  This method is called for each render item on the assocated DAG object
771  whenever the object changes.This method is passed a render item.This
772  method should return true if the indexing for the render item has changed
773  since the last frame.Note that returning false from isIndexingDirty may
774  NOT prevent populate geometry from requiring that an index buffer is
775  updated.
776 
777  Note : Dependency graph evaluation is not allowed in this function.
778  Note : For more information, see MPxGeometryOverride.isIndexingDirty()
779  """
780 
781  ## In this example, we only need to invalite the index data when the input mesh changed
782  return self.fMesh and self.fMesh.evalInputMeshChangedSinceVP2Update()
783 
784  def requiresGeometryUpdate(self):
785  """
786  This method is called once during each draw-preparation phase. If this
787  method returns true then the associated DAG object will have a chance
788  to update its render geometry this frame. (By calling populateGeometry())
789 
790  This code has to be thread safe, non - blocking and work only on
791  data owned by the associated DAG object.
792 
793  Note : Dependency graph evaluation is not allowed in this function.
794  Note: For more information, see MPxGeometryOverride.requiresGeometryUpdate()
795  """
796 
797  ## In this example, there is only two situation where we need update the geometry on the GPU
798  ## 1-) The input mesh connection changed, so we need to update all the vertex data on the gpu.
799  ## 2-) The input mesh geometry changed, so we need to update all the vertex data on the gpu.
800  ## 3-) The size attribute changed, so we need to scale the vertex position on the CPU and
801  ## update the vertex position on the GPU.
802  return self.fMesh and \
803  (self.fMesh.evalInputMeshChangedSinceVP2Update() or \
804  self.fMesh.evalInputMeshGeoChangedSinceVP2Update() or \
805  self.fMesh.evalSizeChangedSinceVP2Update() )
806 
807  def requiresUpdateRenderItems(self, path):
808  """
809  If this method returns false for an MDagPath instance
810  of the associated DAG object then updateRenderItems() will not be called
811  for that MDagPath draw-preparation phase. If requiresUpdateRenderItems()
812  returns false for all MDagPaths then updateRenderItems() will not be
813  called at all for the draw-preparation phase.
814 
815  Note : Dependency graph evaluation is not allowed in this function.
816  Note : For more information, see MPxGeometryOverride.requiresUpdateRenderItems()
817  """
818 
819  if self.fMesh and self.fMesh.evalMaterialChangedSinceVP2Update():
820  self.fMesh.setMaterialChangedSinceVP2Update(False);
821  return True
822 
823  currentDisplayStatus = omr.MGeometryUtilities.displayStatus(path)
824 
825  ## If the display status not changed, then don't do any render item updates.
826  ##
827  ## Note : In a real situation, you will probably want to check if fDisplayStatus changed
828  ## per dag path instance. In this example, we try to keep thing as simple as possible.
829  if (currentDisplayStatus == self.fDisplayStatus):
830  return False
831 
832  return True
833 
834  def supportsEvaluationManagerParallelUpdate(self):
835  """
836  If this method returns true then the MPxGeometryOverride will be considered
837  for Evaluation Manager Parallel Update.
838 
839  Note : Dependency graph evaluation is not allowed in this function.
840  Note : For more information, see MPxGeometryOverride.supportsEvaluationManagerParallelUpdate()
841  """
842 
843  return True
844 
845  def supportsVP2CustomCaching(self):
846  """
847  This method is called at any time during evaluation to determine if VP2
848  Custom Caching is supported.
849 
850  Note : Dependency graph evaluation is not allowed in this function.
851  Note : For more information, see MPxGeometryOverride::supportsVP2CustomCaching()
852  """
853 
854  ## This method must return the same value for the entire lifetime of the object.
855  return True
856 
857  def updateRenderItems(self, path, renderItems):
858  """
859  This method is called for each instance of the associated DAG object whenever
860  the object changes and receive the path to the instance and the current list
861  of render items associated with that instance. Implementations of this method
862  may add, remove or modify items in the list. As an alternative this method
863  can enable or disable items that must be used or not based on some properties.
864 
865  A render item represents a single renderable entity and contain many properties
866  to let the Viewport 2.0 to know how to render the entity. By example, A render
867  item contain a name, a type, the geometry primitive, the geometry buffer and a
868  shader instance.
869 
870  In this example, this functions will create 3 render items to render the object's
871  geometry. The first render item will be use to display the oject with a single color
872  only when the Viewport 2.0 is in shaded or textured mode. The second render item
873  will be use to display the object in wireframe with a single color only when the
874  Viewport 2.0 is in wireframe mode. The last render item will be use to render the
875  object with a single color in wireframe only when the object is selected independing
876  of the Viewport 2.0 display mode. Both wireframe render item will be enable or disable
877  depending of the object selection state.
878 
879  Note : Dependency graph evaluation is not allowed in this function.
880  Note : For more information, see MPxGeometryOverride.updateRenderItems()
881  """
882 
883  if not path.isValid():
884  return
885 
886  shaderManager = omr.MRenderer.getShaderManager()
887  if not shaderManager:
888  return
889 
890  ##
891  ## Look if there is a "StandardShadedItem" render item in the list.
892  ## This render item is created by maya when the object is attached
893  ## to a shader network node.
894  ##
895  standardShadedItemIndex = renderItems.indexOf("StandardShadedItem");
896 
897  renderItemIndex = renderItems.indexOf(self.sCustomShadedItemName)
898 
899  ##
900  ## Create a render item that will render geometry with a single color
901  ## if it didn't already exist.
902  ##
903  ## This render item will be used only in shaded and textured mode.
904  ##
905  if standardShadedItemIndex < 0 and renderItemIndex < 0:
906  ## Create the new render item with the given name.
907  ## We designate this item as a UI Material.
908  ## The "topology" for the render item is a triangle list.
909  shadedRenderItem = omr.MRenderItem.create(self.sCustomShadedItemName,
910  omr.MRenderItem.MaterialSceneItem,
911  omr.MGeometry.kTriangles)
912 
913  ## We want this render item to only show up when in shaded or textured mode
914  shadedRenderItem.setDrawMode(omr.MGeometry.kShaded)
915 
916  ## The the depth priority of this render item to sDormantFilledDepthPriority (The default value).
917  ## Depth priority is usefull when an object is displayed with many render items.
918  ## Viewport 2.0 will use the depth priority to know which render item must be
919  ## draw on top of others.
920  shadedRenderItem.setDepthPriority(omr.MRenderItem.sDormantFilledDepthPriority)
921 
922  ## Enable the render item so it will be use for rendering
923  shadedRenderItem.enable(True)
924 
925  ## Get an instance of a 3dSolidShader from the shader manager.
926  ## The shader tells the graphics hardware how to draw the geometry.
927  ## The MShaderInstance is a reference to a shader along with the
928  ## values for the shader parameters.
929  shader = shaderManager.getFragmentShader("mayaLambertSurface", "outSurfaceFinal", True)
930  if shader:
931  ## Set the shader color parameter
932  blueColor = [ 0.0, 0.0, 1.0, 1.0 ]
933  shader.setParameter("color", blueColor)
934 
935  ## Assign the shader to the render item. This adds a reference to that
936  ## shader.
937  shadedRenderItem.setShader(shader)
938 
939  ## Once assigned, no need to hold on to shader instance
940  shaderManager.releaseShader(shader)
941 
942  ## The item must be added to the persistent list to be considered
943  ## for update / rendering
944  renderItems.append(shadedRenderItem)
945 
946 
947  ##
948  ## If the 'StandardShadedItem' and the custom render item exists,
949  ## enable the 'StandardShadedItem' render item and disable the custom render item.
950  ##
951  ## This situation can happen if the geometry override was first created without a
952  ## shading engine, than a shading enine was attached to the node.
953  ##
954  if standardShadedItemIndex >= 0 and renderItemIndex >= 0:
955  renderItem = renderItems[renderItemIndex]
956  if renderItem:
957  renderItem.enable(False)
958 
959  renderItem = renderItems[standardShadedItemIndex]
960  if renderItem:
961  renderItem.enable(True)
962 
963 
964  ##
965  ## Make sure the custom render item is enable if the 'StandardShadedItem' render item
966  ## does not exists.
967  ##
968  if standardShadedItemIndex < 0 and renderItemIndex >= 0:
969  renderItem = renderItems[renderItemIndex]
970  if renderItem:
971  renderItem.enable(True)
972 
973 
974 
975  ## Get the inherited DAG display properties.
976  self.fWireframeDisplayColor = omr.MGeometryUtilities.wireframeColor(path)
977  self.fDisplayStatus = omr.MGeometryUtilities.displayStatus(path)
978 
979  ## Update the wireframe render item used when the object will be selected
980  isWireFrameRenderItemEnabled = self.fDisplayStatus == omr.MGeometryUtilities.kLead or \
981  self.fDisplayStatus == omr.MGeometryUtilities.kActive
982 
983  self.updateWireframeItems(PerformanceExampleGeometryOverride.sActiveWireframeRenderItemName, \
984  omr.MGeometry.kAll, \
985  omr.MRenderItem.sSelectionDepthPriority, \
986  self.fWireframeDisplayColor, \
987  isWireFrameRenderItemEnabled, \
988  renderItems, \
989  shaderManager)
990 
991  ## Update the wireframe render item used when the object will not be selected
992  isWireFrameRenderItemEnabled = self.fDisplayStatus == omr.MGeometryUtilities.kDormant
993 
994  self.updateWireframeItems(PerformanceExampleGeometryOverride.sDormantWireframeRenderItemName,
995  omr.MGeometry.kWireframe,
996  omr.MRenderItem.sDormantWireDepthPriority,
997  self.fWireframeDisplayColor,
998  isWireFrameRenderItemEnabled,
999  renderItems,
1000  shaderManager)
1001 
1002 
1003  def populateGeometry(self, requirements, renderItems, data):
1004  """
1005  Fill in data and index streams based on the requirements passed in.
1006  Associate indexing with the render items passed in.
1007 
1008  Note : Dependency graph evaluation is not allowed in this function.
1009  Note : For more information, see MPxGeometryOverride.populateGeometry()
1010  """
1011  if not self.fMesh:
1012  return
1013 
1014  if self.fInputMeshPath and self.fInputMeshPath.isValid():
1015  self.populateInputMeshGeometry(requirements, renderItems, data)
1016  else:
1017  self.populateDefaultMeshGeometry(requirements, renderItems, data)
1018 
1019 
1020  ## Now that the current geometry have been updated
1021  ## clear the signal flags.
1022  self.fMesh.setSizeChangedSinceVP2Update(False)
1023  self.fMesh.setInputMeshChangedSinceVP2Update(False)
1024  self.fMesh.setInputMeshGeoChangedSinceVP2Update(False)
1025 
1026 
1027  def populateInputMeshGeometry(self, requirements, renderItems, data):
1028  """
1029  This function is used to populate the geometry data from the
1030  input mesh. It used the MGeometryExtractor to extract
1031  the data.
1032  """
1033 
1034  ##
1035  ## Use MGeometryExtractor to extract geometry from the input mesh path
1036  ##
1037  options = omr.MGeometryExtractor.kPolyGeom_NotSharing
1038  extractor = omr.MGeometryExtractor(requirements, self.fInputMeshPath, options)
1039  if (extractor is None):
1040  return;
1041 
1042  vertexBufferDescriptorList = requirements.vertexRequirements()
1043  for i in xrange(len(vertexBufferDescriptorList)):
1044  desc = vertexBufferDescriptorList[i]
1045 
1046  vertexBuffer = data.createVertexBuffer(desc)
1047  if vertexBuffer:
1048  vertexCount = extractor.vertexCount()
1049 
1050  bufferAdress = vertexBuffer.acquire(vertexCount, True)
1051  if bufferAdress:
1052 
1053  extractor.populateVertexBuffer(bufferAdress, vertexCount, desc)
1054 
1055 
1056  ## For vertex position, scale the position with the size multiplicator
1057  if desc.semantic == omr.MGeometry.kPosition:
1058 
1059  bufferData = ( (ctypes.c_float * 3) * vertexCount ).from_address(bufferAdress)
1060 
1061  for i in xrange(vertexCount):
1062  bufferData[i][0] *= self.fSizeMultiplier
1063  bufferData[i][1] *= self.fSizeMultiplier
1064  bufferData[i][2] *= self.fSizeMultiplier
1065 
1066  ## Transfer from CPU to GPU memory.
1067  vertexBuffer.commit(bufferAdress)
1068 
1069 
1070  ######################################################################
1071  ## Update indexing data for all appropriate render items
1072  ######################################################################
1073  for i in xrange(len(renderItems)):
1074  item = renderItems[i]
1075  if not item:
1076  continue
1077 
1078  indexBuffer = data.createIndexBuffer(omr.MGeometry.kUnsignedInt32)
1079  if not indexBuffer:
1080  continue
1081 
1082  if item.primitive() == omr.MGeometry.kTriangles:
1083  triangleDesc = omr.MIndexBufferDescriptor(omr.MIndexBufferDescriptor.kTriangle, "", omr.MGeometry.kTriangles, 3)
1084 
1085  numTriangles = extractor.primitiveCount(triangleDesc)
1086 
1087  indices = indexBuffer.acquire(3 * numTriangles, True)
1088  if indices:
1089 
1090  ## Fill a buffer with geometry indexing data
1091  extractor.populateIndexBuffer(indices, numTriangles, triangleDesc)
1092 
1093  ## Transfer from CPU to GPU memory.
1094  indexBuffer.commit(indices)
1095 
1096  elif item.primitive() == omr.MGeometry.kLines:
1097  edgeDesc = omr.MIndexBufferDescriptor(omr.MIndexBufferDescriptor.kEdgeLine, "", omr.MGeometry.kLines, 2)
1098 
1099  numEdges = extractor.primitiveCount(edgeDesc)
1100 
1101  indices = indexBuffer.acquire(2 * numEdges, True)
1102  if indices:
1103 
1104  ## Fill a buffer with geometry indexing data
1105  extractor.populateIndexBuffer(indices, numEdges, edgeDesc)
1106 
1107  ## Transfer from CPU to GPU memory.
1108  indexBuffer.commit(indices)
1109 
1110  ## Associate index buffer with render item
1111  item.associateWithIndexBuffer(indexBuffer)
1112 
1113  def populateDefaultMeshGeometry(self, requirements, renderItems, data):
1114  """
1115  This function is used to populate the geometry data from the
1116  default mesh (Hardcoded mesh) in case there is no input mesh
1117  attached to the node.
1118  """
1119  vertexBufferDescriptorList = requirements.vertexRequirements()
1120  for i in xrange(len(vertexBufferDescriptorList)):
1121 
1122  desc = vertexBufferDescriptorList[i]
1123 
1124  if desc.semantic == omr.MGeometry.kPosition:
1125  ##
1126  ## Create and fill the vertex position buffer
1127  #
1128  positionBuffer = data.createVertexBuffer(desc)
1129 
1130  if positionBuffer:
1131  position = self.fMesh.getPositions()
1132 
1133  bufferAdress = positionBuffer.acquire(len(position), True)
1134 
1135  if bufferAdress:
1136  bufferData = ((ctypes.c_float * 3)*len(position)).from_address(bufferAdress)
1137 
1138  for i in xrange(len(position)):
1139  bufferData[i][0] = position[i][0] * self.fSizeMultiplier
1140  bufferData[i][1] = position[i][1] * self.fSizeMultiplier
1141  bufferData[i][2] = position[i][2] * self.fSizeMultiplier
1142 
1143  ## Transfer from CPU to GPU memory.
1144  positionBuffer.commit(bufferAdress)
1145 
1146 
1147  elif desc.semantic == omr.MGeometry.kNormal:
1148  ##
1149  ## Create and fill the vertex normal buffer
1150  ##
1151  normalsBuffer = data.createVertexBuffer(desc)
1152 
1153  if normalsBuffer:
1154  normals = self.fMesh.getNormals()
1155 
1156  bufferAdress = normalsBuffer.acquire(len(normals), True)
1157 
1158  if bufferAdress:
1159  bufferData = ((ctypes.c_float * 3)*len(normals)).from_address(bufferAdress)
1160 
1161  for i in xrange(len(normals)):
1162  bufferData[i][0] = normals[i][0]
1163  bufferData[i][1] = normals[i][1]
1164  bufferData[i][2] = normals[i][2]
1165 
1166  ## Transfer from CPU to GPU memory.
1167  normalsBuffer.commit(bufferAdress)
1168 
1169  elif desc.semantic == omr.MGeometry.kTangent:
1170  ##
1171  ## Create and fill the vertex tangent buffer
1172  ##
1173  buffer = data.createVertexBuffer(desc)
1174 
1175  if buffer:
1176  tangents = self.fMesh.getTangents()
1177 
1178  bufferAdress = buffer.acquire(len(tangents), True)
1179 
1180  if bufferAdress:
1181  bufferData = ((ctypes.c_float * 3)*len(tangents)).from_address(bufferAdress)
1182 
1183  for i in xrange(len(tangents)):
1184  bufferData[i][0] = tangents[i][0]
1185  bufferData[i][1] = tangents[i][1]
1186  bufferData[i][2] = tangents[i][2]
1187 
1188  ## Transfer from CPU to GPU memory.
1189  buffer.commit(bufferAdress)
1190 
1191  elif desc.semantic == omr.MGeometry.kBitangent:
1192  ##
1193  ## Create and fill the vertex bitangent buffer
1194  ##
1195  buffer = data.createVertexBuffer(desc)
1196 
1197  if buffer:
1198  biTangents = self.fMesh.getBiTangents()
1199 
1200  bufferAdress = buffer.acquire(len(biTangents), True)
1201 
1202  if bufferAdress:
1203  bufferData = ((ctypes.c_float * 3)*len(biTangents)).from_address(bufferAdress)
1204 
1205  for i in xrange(len(biTangents)):
1206  bufferData[i][0] = biTangents[i][0]
1207  bufferData[i][1] = biTangents[i][1]
1208  bufferData[i][2] = biTangents[i][2]
1209 
1210  ## Transfer from CPU to GPU memory.
1211  buffer.commit(bufferAdress)
1212 
1213  elif desc.semantic == omr.MGeometry.kTexture:
1214  ##
1215  ## Create and fill the vertex texture coordinates buffer
1216  ##
1217  buffer = data.createVertexBuffer(desc)
1218 
1219  if buffer:
1220  textureCoords = self.fMesh.getTextureCoords()
1221 
1222  bufferAdress = buffer.acquire(len(textureCoords), True)
1223 
1224  if bufferAdress:
1225  bufferData = ((ctypes.c_float * 2)*len(textureCoords)).from_address(bufferAdress)
1226 
1227  for i in xrange(len(textureCoords)):
1228  bufferData[i][0] = textureCoords[i][0]
1229  bufferData[i][1] = textureCoords[i][1]
1230 
1231  ## Transfer from CPU to GPU memory.
1232  buffer.commit(bufferAdress)
1233 
1234 
1235 
1236 
1237  ####################################################################
1238  ## Update indexing data for all appropriate render items
1239  #####################################################################
1240  for i in xrange(len(renderItems)):
1241  item = renderItems[i]
1242  if not item:
1243  continue
1244 
1245  if item.primitive() == omr.MGeometry.kTriangles:
1246  ##
1247  ## Create and fill the index buffer used to render triangles
1248  ##
1249  indexBuffer = data.createIndexBuffer(omr.MGeometry.kUnsignedInt32)
1250  if indexBuffer:
1251  indices = self.fMesh.getIndices()
1252  bufferAdress = indexBuffer.acquire(len(indices), True)
1253 
1254  if bufferAdress:
1255  bufferData = (ctypes.c_uint * len(indices)).from_address(bufferAdress)
1256 
1257  for i in xrange(len(indices)):
1258  bufferData[i] = indices[i]
1259 
1260  ## Transfer from CPU to GPU memory.
1261  indexBuffer.commit(bufferAdress)
1262 
1263  ## Associate index buffer with render item
1264  item.associateWithIndexBuffer(indexBuffer)
1265 
1266 
1267  elif item.primitive() == omr.MGeometry.kLines:
1268  ##
1269  ## Create and fill the index buffer used to render lines (Wireframe)
1270  ##
1271  indexBuffer = data.createIndexBuffer(omr.MGeometry.kUnsignedInt32)
1272  if indexBuffer:
1273  indices = self.fMesh.getIndices()
1274  bufferAdress = indexBuffer.acquire(len(indices), True)
1275 
1276  if bufferAdress:
1277  bufferData = (ctypes.c_uint * len(indices)).from_address(bufferAdress)
1278 
1279  for i in xrange(len(indices)):
1280  bufferData[i] = indices[i]
1281 
1282  ## Transfer from CPU to GPU memory.
1283  indexBuffer.commit(bufferAdress)
1284 
1285  ## Associate index buffer with render item
1286  item.associateWithIndexBuffer(indexBuffer)
1287 
1288  def cleanUp(self):
1289  #
1290  ## Clean up any cached data stored from the updateDG() phase.
1291  ##
1292  ## Note : For more information, see MPxGeometryOverride.cleanUp()
1293  ##
1294 
1295  ## Nothing here because the plugin doesn't keep any temporary data.
1296  return None
1297 
1298  def updateWireframeItems(self, renderItemName, drawMode, depthPriority, color, isEnabled, renderItemList, shaderManager):
1299  """
1300  ## Update the wireframe render item named 'renderItemName' or create it
1301  ## if it doesn't exists.
1302  ##
1303  ## Note : Arguments drawMode and depthPriority are only used for creation of
1304  ## the render item.
1305  """
1306 
1307  renderItem = None
1308 
1309  ## Try to find the active wireframe render item.
1310  ## If the returning index is smaller than 0, that means
1311  ## the render item does't exists yet. So, create it.
1312  renderItemIndex = renderItemList.indexOf(renderItemName)
1313  if renderItemIndex < 0:
1314  ## Create the new render item with the given name.
1315  ## We designate this item as a UI "decoration" and will not be
1316  ## involved in rendering aspects such as casting shadows
1317  ## The "topology" for the render item is a line list.
1318  renderItem = omr.MRenderItem.create(renderItemName, \
1319  omr.MRenderItem.DecorationItem, \
1320  omr.MGeometry.kLines)
1321 
1322  ## We want this render item to show up when in all mode
1323  ## ( Wireframe, Shaded, Textured and BoundingBox)
1324  renderItem.setDrawMode(drawMode)
1325 
1326  ## Set selection priority: on top of everything
1327  renderItem.setDepthPriority(depthPriority)
1328 
1329  ## Get an instance of a 3dSolidShader from the shader manager.
1330  ## The shader tells the graphics hardware how to draw the geometry.
1331  ## The MShaderInstance is a reference to a shader along with the
1332  ## values for the shader parameters.
1333  shader = shaderManager.getStockShader(omr.MShaderManager.k3dSolidShader)
1334  if shader:
1335  ## Assign the shader to the render item. This adds a reference to that
1336  ## shader.
1337  renderItem.setShader(shader)
1338 
1339  ## Once assigned, no need to hold on to shader instance
1340  shaderManager.releaseShader(shader)
1341 
1342  ## The item must be added to the persistent list to be considered
1343  ## for update / rendering
1344  renderItemList.append(renderItem)
1345  else:
1346  renderItem = renderItemList[renderItemIndex]
1347 
1348 
1349  if renderItem:
1350  shader = renderItem.getShader()
1351  if shader:
1352  ##Set the shader color parameter
1353  shader.setParameter("solidColor", color)
1354 
1355  renderItem.enable(isEnabled)
1356 
1357 
1358 ##
1359 ## Empty implementation of MPxSurfaceShapeUI
1360 ## This is needed when registering the node shape.
1361 ## Otherwise, MFnPlugin.registerShape will failed in python
1362 ##
1363 class GeometryOverrideHighPerformance_shapeUI(omui.MPxSurfaceShapeUI):
1364 
1365  @staticmethod
1366  def creator():
1367  return GeometryOverrideHighPerformance_shapeUI()
1368 
1369  def __init__(self):
1370  omui.MPxSurfaceShapeUI.__init__(self)
1371 
1372 
1373 
1374 
1375 ##===========================================================================
1376 ##
1377 ## initialize / uninitialize plugin
1378 ##
1379 ##===========================================================================
1380 
1381 
1382 def maya_useNewAPI():
1383  """
1384  The presence of this function tells Maya that the plugin produces, and
1385  expects to be passed, objects created using the Maya Python API 2.0.
1386  """
1387  pass
1388 
1389 
1390 sDrawDbClassification = "drawdb/geometry/performanceExample_py"
1391 sDrawRegistrantId = "performanceExamplePlugin_py"
1392 
1393 
1394 def initializePlugin(obj):
1395  plugin = om.MFnPlugin(obj, "Autodesk", "1.0", "Any")
1396 
1397  global sDrawDbClassification, sDrawRegistrantId
1398 
1399  try:
1400  plugin.registerShape("geometryOverrideHighPerformance_shape_py", \
1401  GeometryOverrideHighPerformance_shape.id, \
1402  GeometryOverrideHighPerformance_shape.creator, \
1403  GeometryOverrideHighPerformance_shape.initialize, \
1404  GeometryOverrideHighPerformance_shapeUI.creator, \
1405  sDrawDbClassification)
1406  except:
1407  sys.stderr.write("Failed to register geometryOverrideHighPerformance_shape_py\n")
1408  raise
1409 
1410 
1411  try:
1412  omr.MDrawRegistry.registerGeometryOverrideCreator(sDrawDbClassification, \
1413  sDrawRegistrantId, \
1414  PerformanceExampleGeometryOverride.creator)
1415  except:
1416  sys.stderr.write("Failed to register override\n")
1417  raise
1418 
1419 
1420 def uninitializePlugin(obj):
1421  plugin = om.MFnPlugin(obj)
1422 
1423  global sUseSubSceneOverride, sDrawDbClassification, sDrawRegistrantId
1424 
1425 
1426  try:
1427  omr.MDrawRegistry.deregisterGeometryOverrideCreator(sDrawDbClassification, sDrawRegistrantId)
1428  except:
1429  sys.stderr.write("Failed to deregister override\n")
1430  pass
1431 
1432 
1433  try:
1434  plugin.deregisterNode(GeometryOverrideHighPerformance_shape.id)
1435  except:
1436  sys.stderr.write("Failed to deregister node\n")
1437  pass