scripted/pyCustomPrimitiveGenerator.py

scripted/pyCustomPrimitiveGenerator.py
1 # Copyright 2015 Autodesk, Inc. All rights reserved.
2 #
3 # Use of this software is subject to the terms of the Autodesk
4 # license agreement provided at the time of installation or download,
5 # or which otherwise accompanies this software in either electronic
6 # or hard copy form.
7 
8 from ctypes import *
9 import maya.api.OpenMayaRender as omr
10 import maya.api.OpenMaya as om
11 
12 # Example plugin: pyCustomPrimitiveGenerator.py
13 #
14 # This plug-in is an example of a custom MPxPrimitiveGenerator.
15 # It provides custom primitives based on shader requirements coming from
16 # an MPxShaderOverride. The name() in the MIndexBufferDescriptor is used
17 # to signify a unique identifier for a custom buffer.
18 
19 # This primitive generator is provided for demonstration purposes only.
20 # It simply provides a triangle list for mesh objects with no vertex sharing.
21 # A more sophisticated primitive provider could be used to provide patch primitives
22 # for GPU tessellation.
23 
24 # This plugin is meant to be used in conjunction with the dx11Shader, cgfxShader, glslShader or the hwPhongShader plugins.
25 # The customPrimitiveGeneratorDX11.fx (dx11Shader), customPrimitiveGeneratorGL.cgfx (cgfxShader)
26 # and customPrimitiveGenerator.osgfx (glslShader) files accompanying this sample
27 # can be loaded using the appropriate shader plugin.
28 # In any case, the environment variable MAYA_USE_CUSTOMPRIMITIVEGENERATOR needs to be set (any value is fine) for it to be enabled.
29 
30 def maya_useNewAPI():
31  """
32  The presence of this function tells Maya that the plugin produces, and
33  expects to be passed, objects created using the Maya Python API 2.0.
34  """
35  pass
36 
37 
38 class MyCustomPrimitiveGenerator(omr.MPxPrimitiveGenerator):
39  def __init__(self):
40  omr.MPxPrimitiveGenerator.__init__(self)
41 
42  def computeIndexCount(self, object, component):
43  # get the mesh from the object
44  mesh = om.MFnMesh(object)
45 
46  return mesh.numFaceVertices()
47 
48  def generateIndexing(self, object, component, sourceIndexing, targetIndexing, indexBuffer):
49  # get the mesh from the object
50  mesh = om.MFnMesh(object)
51 
52  for x in range(0, len(targetIndexing)):
53  target = targetIndexing[x]
54  if target != None and target.componentType() == omr.MComponentDataIndexing.kFaceVertex:
55  # Get Triangles
56  (triCounts, triVertIDs) = mesh.getTriangleOffsets()
57  numTriVerts = len(triVertIDs)
58 
59  customNumTriVerts = numTriVerts * 2
60  indexData = indexBuffer.acquire(customNumTriVerts, True) # writeOnly = True - we don't need the current buffer values
61  if indexData == 0:
62  return omr.MGeometry.kInvalidPrimitive
63 
64  sharedIndices = target.indices()
65 
66  # Crawl the sharedIndices array to find the last
67  # The new vertices will be added at the end
68  nextNewVertexIndex = 0
69  for idx in range(0, len(sharedIndices)):
70  if sharedIndices[idx] > nextNewVertexIndex:
71  nextNewVertexIndex = sharedIndices[idx]
72  nextNewVertexIndex += 1
73 
74  dataType = indexBuffer.dataType()
75 
76  address = indexData
77  uintinc = sizeof(c_uint)
78  ushortinc = sizeof(c_ushort)
79 
80  newTriId = 0
81  triId = 0
82  while triId < numTriVerts:
83  # split each triangle in two : add new vertex between vertexId1 and vertexId2
84  vertexId0 = sharedIndices[triVertIDs[triId]]
85  vertexId1 = sharedIndices[triVertIDs[triId+1]]
86  vertexId2 = sharedIndices[triVertIDs[triId+2]]
87  triId += 3
88 
89  newVertexIndex = nextNewVertexIndex
90  nextNewVertexIndex += 1
91 
92  # Triangle 0 1 2 become two triangles : 0 1 X and 0 X 2
93  if dataType == omr.MGeometry.kUnsignedInt32:
94  xaddr = address
95  yaddr = address+uintinc
96  zaddr = address+2*uintinc
97  address += 3*uintinc
98 
99  c_uint.from_address(xaddr).value = vertexId0
100  c_uint.from_address(yaddr).value = vertexId1
101  c_uint.from_address(zaddr).value = newVertexIndex
102 
103  xaddr = address
104  yaddr = address+uintinc
105  zaddr = address+2*uintinc
106  address += 3*uintinc
107 
108  c_uint.from_address(xaddr).value = vertexId0
109  c_uint.from_address(yaddr).value = newVertexIndex
110  c_uint.from_address(zaddr).value = vertexId2
111 
112  elif dataType == omr.MGeometry.kUnsignedChar:
113  xaddr = address
114  yaddr = address+ushortinc
115  zaddr = address+2*ushortinc
116  address += 3*ushortinc
117 
118  c_ushort.from_address(xaddr).value = vertexId0
119  c_ushort.from_address(yaddr).value = vertexId1
120  c_ushort.from_address(zaddr).value = newVertexIndex
121 
122  xaddr = address
123  yaddr = address+ushortinc
124  zaddr = address+2*ushortinc
125  address += 3*ushortinc
126 
127  c_ushort.from_address(xaddr).value = vertexId0
128  c_ushort.from_address(yaddr).value = newVertexIndex
129  c_ushort.from_address(zaddr).value = vertexId2
130 
131  indexBuffer.commit(indexData)
132 
133  return (omr.MGeometry.kTriangles, 3)
134  return omr.MGeometry.kInvalidPrimitive
135 
136 # This is the primitive generator creation function registered with the DrawRegistry.
137 # Used to initialize a custom primitive generator.
138 def createMyCustomPrimitiveGenerator():
139  return MyCustomPrimitiveGenerator()
140 
141 ##########################################################################################
142 ##########################################################################################
143 
144 class MyCustomPositionBufferGenerator(omr.MPxVertexBufferGenerator):
145  def __init__(self):
146  omr.MPxVertexBufferGenerator.__init__(self)
147 
148  def getSourceIndexing(self, object, sourceIndexing):
149  # get the mesh from the object
150  mesh = om.MFnMesh(object)
151 
152  (vertexCount, vertexList) = mesh.getVertices()
153  vertCount = len(vertexList)
154 
155  vertices = sourceIndexing.indices()
156  vertices.setLength(vertCount)
157 
158  for i in range(0, vertCount):
159  vertices[i] = vertexList[i]
160 
161  # assign the source indexing
162  sourceIndexing.setComponentType(omr.MComponentDataIndexing.kFaceVertex)
163 
164  return True
165 
166  def getSourceStreams(self, object, sourceStreams):
167  #No source stream needed
168  return False
169 
170  def createVertexStream(self, object, vertexBuffer, targetIndexing, sharedIndexing, sourceStreams):
171  # get the descriptor from the vertex buffer.
172  # It describes the format and layout of the stream.
173  descriptor = vertexBuffer.descriptor()
174 
175  # we are expecting a float stream.
176  if descriptor.dataType != omr.MGeometry.kFloat:
177  return
178 
179  # we are expecting a dimension of 3
180  dimension = descriptor.dimension
181  if dimension != 3:
182  return
183 
184  # we are expecting a position channel
185  if descriptor.semantic != omr.MGeometry.kPosition:
186  return
187 
188  # get the mesh from the current path
189  # if it is not a mesh we do nothing.
190  mesh = om.MFnMesh(object)
191 
192  indices = targetIndexing.indices()
193 
194  vertexCount = len(indices)
195  if vertexCount <= 0:
196  return
197 
198  # Keep track of the vertices that will be used to created a new vertex in-between
199  extraVertices = []
200  # Get Triangles
201  (triCounts, triVertIDs) = mesh.getTriangleOffsets()
202  numTriVerts = len(triVertIDs)
203 
204  sharedIndices = sharedIndexing.indices()
205 
206  triId = 0
207  while triId < numTriVerts:
208  # split each triangle in two : add new vertex between vertexId1 and vertexId2
209  #vertexId0 = sharedIndices[triVertIDs[triId]]
210  vertexId1 = sharedIndices[triVertIDs[triId+1]]
211  vertexId2 = sharedIndices[triVertIDs[triId+2]]
212  triId += 3
213 
214  extraVertices.append( [vertexId1, vertexId2] )
215 
216  newVertexCount = vertexCount + len(extraVertices)
217  customBuffer = vertexBuffer.acquire(newVertexCount, True) # writeOnly = True - we don't need the current buffer values
218  if customBuffer != 0:
219 
220  address = customBuffer
221  floatinc = sizeof(c_float)
222 
223  # Append 'real' vertices position
224  vertId = 0
225  while vertId < vertexCount:
226  vertexId = indices[vertId]
227 
228  point = mesh.getPoint(vertexId)
229 
230  c_float.from_address(address + (vertId * dimension) * floatinc).value = point.x
231  c_float.from_address(address + (vertId * dimension + 1) * floatinc).value = point.y
232  c_float.from_address(address + (vertId * dimension + 2) * floatinc).value = point.z
233 
234  vertId += 1
235 
236  # Append the new vertices position, interpolated from vert1 and vert2
237  for i in range(0, len(extraVertices)):
238  vertexId1 = indices[extraVertices[i][0]]
239  vertexId2 = indices[extraVertices[i][1]]
240 
241  point = mesh.getPoint(vertexId1)
242  point += mesh.getPoint(vertexId2)
243  point /= 2.0
244 
245  c_float.from_address(address + (vertId * dimension) * floatinc).value = point.x
246  c_float.from_address(address + (vertId * dimension + 1) * floatinc).value = point.y
247  c_float.from_address(address + (vertId * dimension + 2) * floatinc).value = point.z
248 
249  vertId += 1
250 
251  vertexBuffer.commit(customBuffer)
252 
253 # This is the buffer generator creation function registered with the DrawRegistry.
254 # Used to initialize the generator.
255 def createMyCustomPositionBufferGenerator():
256  return MyCustomPositionBufferGenerator()
257 
258 ##########################################################################################
259 ##########################################################################################
260 
261 class MyCustomNormalBufferGenerator(omr.MPxVertexBufferGenerator):
262  def __init__(self):
263  omr.MPxVertexBufferGenerator.__init__(self)
264 
265  def getSourceIndexing(self, object, sourceIndexing):
266  # get the mesh from the object
267  mesh = om.MFnMesh(object)
268 
269  (vertexCount, vertexList) = mesh.getVertices()
270  vertCount = len(vertexList)
271 
272  vertices = sourceIndexing.indices()
273  vertices.setLength(vertCount)
274 
275  for i in range(0, vertCount):
276  vertices[i] = vertexList[i]
277 
278  # assign the source indexing
279  sourceIndexing.setComponentType(omr.MComponentDataIndexing.kFaceVertex)
280 
281  return True
282 
283  def getSourceStreams(self, object, sourceStreams):
284  #No source stream needed
285  return False
286 
287  def createVertexStream(self, object, vertexBuffer, targetIndexing, sharedIndexing, sourceStreams):
288  # get the descriptor from the vertex buffer.
289  # It describes the format and layout of the stream.
290  descriptor = vertexBuffer.descriptor()
291 
292  # we are expecting a float stream.
293  if descriptor.dataType != omr.MGeometry.kFloat:
294  return
295 
296  # we are expecting a dimension of 3
297  dimension = descriptor.dimension
298  if dimension != 3:
299  return
300 
301  # we are expecting a normal channel
302  if descriptor.semantic != omr.MGeometry.kNormal:
303  return
304 
305  # get the mesh from the current path
306  # if it is not a mesh we do nothing.
307  mesh = om.MFnMesh(object)
308 
309  indices = targetIndexing.indices()
310 
311  vertexCount = len(indices)
312  if vertexCount <= 0:
313  return
314 
315  # Keep track of the vertices that will be used to created a new vertex in-between
316  extraVertices = []
317  # Get Triangles
318  (triCounts, triVertIDs) = mesh.getTriangleOffsets()
319  numTriVerts = len(triVertIDs)
320 
321  sharedIndices = sharedIndexing.indices()
322 
323  triId = 0
324  while triId < numTriVerts:
325  # split each triangle in two : add new vertex between vertexId1 and vertexId2
326  #vertexId0 = sharedIndices[triVertIDs[triId]]
327  vertexId1 = sharedIndices[triVertIDs[triId+1]]
328  vertexId2 = sharedIndices[triVertIDs[triId+2]]
329  triId += 3
330 
331  extraVertices.append( [vertexId1, vertexId2] )
332 
333  newVertexCount = vertexCount + len(extraVertices)
334  customBuffer = vertexBuffer.acquire(newVertexCount, True) # writeOnly = True - we don't need the current buffer values
335  if customBuffer != 0:
336 
337  address = customBuffer
338  floatinc = sizeof(c_float)
339 
340  normals = mesh.getNormals()
341 
342  # Append 'real' vertices position
343  vertId = 0
344  while vertId < vertexCount:
345  vertexId = indices[vertId]
346 
347  normal = normals[vertId]
348 
349  c_float.from_address(address + (vertId * dimension) * floatinc).value = normal.x
350  c_float.from_address(address + (vertId * dimension + 1) * floatinc).value = normal.y
351  c_float.from_address(address + (vertId * dimension + 2) * floatinc).value = normal.z
352 
353  vertId += 1
354 
355  # Append the new vertices normal, interpolated from vert1 and vert2
356  for i in range(0, len(extraVertices)):
357  vertexId1 = extraVertices[i][0]
358  vertexId2 = extraVertices[i][1]
359 
360  normal = normals[vertexId1]
361  normal += normals[vertexId2]
362  normal /= 2.0
363 
364  c_float.from_address(address + (vertId * dimension) * floatinc).value = normal.x
365  c_float.from_address(address + (vertId * dimension + 1) * floatinc).value = normal.y
366  c_float.from_address(address + (vertId * dimension + 2) * floatinc).value = normal.z
367 
368  vertId += 1
369 
370  vertexBuffer.commit(customBuffer)
371 
372 # This is the buffer generator creation function registered with the DrawRegistry.
373 # Used to initialize the generator.
374 def createMyCustomNormalBufferGenerator():
375  return MyCustomNormalBufferGenerator()
376 
377 ##########################################################################################
378 ##########################################################################################
379 
380 # The following routines are used to register/unregister
381 # the vertex mutators with Maya
382 
383 def initializePlugin(obj):
384 
385  omr.MDrawRegistry.registerPrimitiveGenerator("customPrimitiveTest", createMyCustomPrimitiveGenerator)
386 
387  omr.MDrawRegistry.registerVertexBufferGenerator("customPositionStream", createMyCustomPositionBufferGenerator)
388 
389  omr.MDrawRegistry.registerVertexBufferGenerator("customNormalStream", createMyCustomNormalBufferGenerator)
390 
391 def uninitializePlugin(obj):
392 
393  omr.MDrawRegistry.deregisterPrimitiveGenerator("customPrimitiveTest")
394 
395  omr.MDrawRegistry.deregisterVertexBufferGenerator("customPositionStream")
396 
397  omr.MDrawRegistry.deregisterVertexBufferGenerator("customNormalStream")
398