レンダーされる出力: 次のレンダー イメージは、球のメッシュをボクセル化することによって取得しました。元の球に青い Lambert マテリアルを適用し、ボクセル化したメッシュに赤いサーフェス シェーダを適用しました。ボクセル化したメッシュは、線幅の値が 0.223 のトゥーン アウトラインにも割り当てられています。
プログラムの概要: 次のプラグイン コードによって作成される新しい MPxNode は、入力として、メッシュとともに、ボクセルの幅とボクセル間距離を決定するユーザ定義アトリビュートを受け取ります。このボクセライザ ノードは、オリジナルのメッシュによって占有されるボリュームを塗り潰す立方体で構成される新しいメッシュに入力メッシュを変換します。ボクセル化プロセスは、MFnMesh.allIntersections() を使用して、オリジナルのメッシュのバウンディング ボックス内側からカメラに向かって光線を投影します。メッシュが閉じていると仮定すると、光線がメッシュのサーフェスに沿って奇数のポイントと交差している場合、光線の原点はメッシュ内に含まれます。
# pyVoxelizerNode.py import sys import maya.api.OpenMaya as OpenMaya def maya_useNewAPI(): """ The presence of this function tells Maya that the plugin produces, and expects to be passed, objects created using the Maya Python API 2.0. """ pass # Plug-in information: kPluginNodeName = 'voxelizerNode' # The name of the node. kPluginNodeId = OpenMaya.MTypeId( 0xBEEF6 ) # A unique ID associated to this node type. # Default input values. defaultVoxelWidth = 0.9 # The width of a cubic voxel. defaultVoxelDistance = 1.0 # The distance which separates the center of two adjacent voxels. ########################################################## # Plug-in ########################################################## class VoxelizerNode(OpenMaya.MPxNode): # Static variables which will later be replaced by the node's attributes. voxelWidthAttribute = OpenMaya.MObject() voxelDistanceAttribute = OpenMaya.MObject() inputMeshAttribute = OpenMaya.MObject() outputMeshAttribute = OpenMaya.MObject() def __init__(self): ''' Constructor. ''' # (!) Make sure you call the base class's constructor. OpenMaya.MPxNode.__init__(self) def compute(self, pPlug, pDataBlock): ''' Here, we will create a voxelized version of the input mesh. ''' if( pPlug == VoxelizerNode.outputMeshAttribute ): # Get our custom input node attributes and values. voxelWidthHandle = pDataBlock.inputValue( VoxelizerNode.voxelWidthAttribute ) voxelWidth = voxelWidthHandle.asFloat() voxelDistanceHandle = pDataBlock.inputValue( VoxelizerNode.voxelDistanceAttribute ) voxelDistance = voxelDistanceHandle.asFloat() inputMeshHandle = pDataBlock.inputValue( VoxelizerNode.inputMeshAttribute ) inputMeshObj = inputMeshHandle.asMesh() # Compute the bounding box around the mesh vertices. boundingBox = self.getBoundingBox( inputMeshObj ) # Determine which voxel centerpoints are contained within the mesh. voxels = self.getVoxels( voxelDistance, inputMeshObj, boundingBox ) # Create a mesh data container, which will store our new voxelized mesh. meshDataFn = OpenMaya.MFnMeshData() newOutputMeshData = meshDataFn.create() # Create a cubic polygon for each voxel and populate the 'newOutputMeshData' MeshData object. self.createVoxelMesh( voxels, voxelWidth, newOutputMeshData) # Set the output data. outputMeshHandle = pDataBlock.outputValue( VoxelizerNode.outputMeshAttribute ) outputMeshHandle.setMObject( newOutputMeshData ) else: return OpenMaya.kUnknownParameter def getBoundingBox(self, pMeshObj): ''' Calculate a bounding box around the mesh vertices. ''' # Create the bounding box object we will populate with the points of the mesh. boundingBox = OpenMaya.MBoundingBox() meshFn = OpenMaya.MFnMesh( pMeshObj ) pointArray = OpenMaya.MPointArray() # Get the points of the mesh in its local coordinate space. pointArray = meshFn.getPoints( OpenMaya.MSpace.kTransform ) for i in range( 0, len(pointArray) ): point = pointArray[i] boundingBox.expand( point ) return boundingBox def getVoxels(self, pVoxelDistance, pMeshObj, pBoundingBox): ''' Obtain a list of voxels as a set of (x,y,z) coordinates in the mesh local space. We obtain these voxels by casting rays from points separated pVoxelDistance apart within the mesh bounding box, and test whether or not these points are contained within the mesh. A point is contained within a closed mesh if the ray shot from the point intersects an odd number of times with the surface of the mesh. ''' # Initialize a list of voxels contained within the mesh. voxels = [] # Get a reference to the MFnMesh function set, and use it on the given mesh object. meshFn = OpenMaya.MFnMesh( pMeshObj ) # Compute an offset which we will apply to the min and max corners of the bounding box. halfVoxelDist = 0.5 * pVoxelDistance # Offset the position of the minimum point to account for the inter-voxel distance. minPoint = pBoundingBox.min minPoint.x += halfVoxelDist minPoint.y += halfVoxelDist minPoint.z += halfVoxelDist # Offset the position of the maximum point to account for the inter-voxel distance. maxPoint = pBoundingBox.max maxPoint.x += halfVoxelDist maxPoint.y += halfVoxelDist maxPoint.z += halfVoxelDist # Define an iterator which will allow us to step through the pVoxelDistance # point intervals contained within our bounding box. We use this iterator # in the for loops that follow to visit each voxel center in the bounding box. def floatIterator(start, stop, step): r = start while r < stop: yield r r += step # Iterate over every point in the bounding box, stepping by pVoxelDistance... for xCoord in floatIterator( minPoint.x, maxPoint.x, pVoxelDistance ): for yCoord in floatIterator( minPoint.y, maxPoint.y, pVoxelDistance ): for zCoord in floatIterator( minPoint.z, maxPoint.z, pVoxelDistance ): # 2D representation of a ray cast from the point within the bounding box: # # (+) ^----------------- # | | # y | | - We are shooting the ray from the point: [*] # axis | <======[*] | - The direction of the ray is parallel to the -Z axis. # | | # | | # (-) ------------------> # (-) z axis (+) # # If the ray intersects with an odd number of points along the surface of the mesh, the # point is contained within the mesh (assuming a closed mesh). raySource = OpenMaya.MFloatPoint( xCoord, yCoord, zCoord ) rayDirection = OpenMaya.MFloatVector( 0, 0, -1 ) # intersectionPoints = OpenMaya.MFloatPointArray() tolerance = 0.0001 ret = meshFn.allIntersections( raySource, # raySource - where we are shooting the ray from. rayDirection, # rayDirection - the direction in which we are shooting the ray. OpenMaya.MSpace.kTransform, # coordinate space - the mesh's local coordinate space. float(9999), # maxParam - the range of the ray. False, # testBothDirections - we are not checking both directions from the raySource tolerance=tolerance, # tolerance - a numeric tolerance threshold which allow intersections to occur just outside the mesh. ) # Returns a tuple of: # -> (hitPoints, hitRayParams, hitFaces, hitTriangles, hitBary1s, hitBary2s) # If there is an odd number of intersection points, then the point lies within the mesh. Otherwise, # the point lies outside the mesh. We are only concerned with voxels whose centerpoint lies within the mesh if( len(ret[0]) % 2 == 1 ): voxels.append( raySource ) # Return the list of voxel coordinates which lie within the mesh. return voxels def createVoxelMesh(self, pVoxelPositions, pVoxelWidth, pOutMeshData): ''' Create a mesh containing one cubic polygon for each voxel in the pVoxelPositions list. ''' numVoxels = len( pVoxelPositions ) numVerticesPerVoxel = 8 # a cube has eight vertices. numPolygonsPerVoxel = 6 # a cube has six faces. numVerticesPerPolygon = 4 # four vertices are required to define a face of a cube. numPolygonConnectsPerVoxel = numPolygonsPerVoxel * numVerticesPerPolygon # 24 # Initialize the required arrays used to create the mesh in MFnMesh.create() totalVertices = numVoxels * numVerticesPerVoxel vertexArray = OpenMaya.MFloatPointArray() vertexArray.setLength( totalVertices ) vertexIndexOffset = 0 totalPolygons = numVoxels * numPolygonsPerVoxel polygonCounts = OpenMaya.MIntArray() polygonCounts.setLength( totalPolygons ) polygonCountsIndexOffset = 0 totalPolygonConnects = numVoxels * numPolygonConnectsPerVoxel polygonConnects = OpenMaya.MIntArray() polygonConnects.setLength( totalPolygonConnects ) polygonConnectsIndexOffset = 0 # Populate the required arrays used in MFnMesh.create() for i in range( 0, numVoxels ): voxelPosition = pVoxelPositions[i] # Add a new cube to the arrays. self.createCube( voxelPosition, pVoxelWidth, vertexArray, vertexIndexOffset, numVerticesPerVoxel, polygonCounts, polygonCountsIndexOffset, numPolygonsPerVoxel, numVerticesPerPolygon, polygonConnects, polygonConnectsIndexOffset ) # Increment the respective index offsets. vertexIndexOffset += numVerticesPerVoxel polygonCountsIndexOffset += numPolygonsPerVoxel polygonConnectsIndexOffset += numPolygonConnectsPerVoxel # Create the mesh now that the arrays have been populated. The mesh is stored in pOutMeshData meshFn = OpenMaya.MFnMesh() meshFn.create( vertexArray, polygonCounts, polygonConnects, parent=pOutMeshData ) def createCube(self, pVoxelPosition, pWidth, pVertexArray, pVertexIndexOffset, pNumVerticesPerVoxel, pPolygonCountArray, pPolygonCountIndexOffset, pNumPolygonsPerVoxel, pNumVerticesPerPolygon, pPolygonConnectsArray, pPolygonConnectsIndexOffset ): ''' Add a cubic polygon to the specified arrays. ''' # We are using half the given width to compute the vertices of the cube. halfWidth = float( pWidth / 2.0 ) # Declare the eight corners of the cube. The cube is centered at pVoxelPosition. vertices = [OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 0 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 1 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 2 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 3 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 4 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 5 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 6 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z)] # 7 # Declare the data structure which binds each vertex to a polygon corner polygonConnections = [ (0, 12, 16), # the vertex indexed at 0 corresponds to the polygon corners whose indexes are (0, 12, 16) in pPolygonConnectsArray. (1, 19, 20), (2, 9, 23), (3, 8, 13), (4, 15, 17), (5, 11, 14), (6, 10, 22), (7, 18, 21) ] # Store the eight corners of the cube in the vertex array. for i in range( 0, pNumVerticesPerVoxel ): # Store the vertex in the passed vertex array. pVertexArray[pVertexIndexOffset + i] = vertices[i] # Assign the vertex in the pVertexArray to the relevant polygons. for polygonConnectionIndex in polygonConnections[i]: pPolygonConnectsArray[pPolygonConnectsIndexOffset + polygonConnectionIndex] = pVertexIndexOffset + i # Declare the number of vertices for each face. for i in range( 0, pNumPolygonsPerVoxel ): # Set the number of vertices for the polygon at the given index. pPolygonCountArray[pPolygonCountIndexOffset + i] = pNumVerticesPerPolygon ########################################################## # Plug-in initialization. ########################################################## def nodeCreator(): ''' Creates an instance of our node class and delivers it to Maya as a pointer. ''' return VoxelizerNode() def nodeInitializer(): ''' Defines the input and output attributes as static variables in our plug-in class. ''' # The following MFnNumericAttribute function set will allow us to create our attributes. numericAttributeFn = OpenMaya.MFnNumericAttribute() # This one allows us to create our input and output mesh attributes. typedAttributeFn = OpenMaya.MFnTypedAttribute() #================================== # INPUT NODE ATTRIBUTE(S) #================================== # We will need a voxel width. global defaultVoxelWidth VoxelizerNode.voxelWidthAttribute = numericAttributeFn.create( 'voxelWidth', 'vw', OpenMaya.MFnNumericData.kFloat, defaultVoxelWidth ) numericAttributeFn.writable = True numericAttributeFn.readable = False numericAttributeFn.storable = True numericAttributeFn.hidden = False numericAttributeFn.setMin( 0.1 ) VoxelizerNode.addAttribute( VoxelizerNode.voxelWidthAttribute ) # We will need a voxel distance value (higher values means we can see more voxels within the volume of the mesh). global defaultVoxelDistance VoxelizerNode.voxelDistanceAttribute = numericAttributeFn.create( 'voxelDistance', 'vd', OpenMaya.MFnNumericData.kFloat, defaultVoxelDistance ) numericAttributeFn.writable = True numericAttributeFn.readable = False numericAttributeFn.storable = True numericAttributeFn.hidden = False numericAttributeFn.setMin( 0.1 ) VoxelizerNode.addAttribute( VoxelizerNode.voxelDistanceAttribute ) # We will need an input mesh attribute. VoxelizerNode.inputMeshAttribute = typedAttributeFn.create( 'inputMesh', 'im', OpenMaya.MFnData.kMesh ) typedAttributeFn.writable = True typedAttributeFn.readable = False typedAttributeFn.storable = False typedAttributeFn.hidden = False VoxelizerNode.addAttribute( VoxelizerNode.inputMeshAttribute ) #================================== # OUTPUT NODE ATTRIBUTE(S) #================================== VoxelizerNode.outputMeshAttribute = typedAttributeFn.create( 'outputMesh', 'om', OpenMaya.MFnData.kMesh ) typedAttributeFn.writable = False typedAttributeFn.readable = True typedAttributeFn.storable = False typedAttributeFn.hidden = False VoxelizerNode.addAttribute( VoxelizerNode.outputMeshAttribute ) #================================== # NODE ATTRIBUTE DEPENDENCIES #================================== # If any of the inputs change, the output mesh will be recomputed. VoxelizerNode.attributeAffects( VoxelizerNode.voxelWidthAttribute, VoxelizerNode.outputMeshAttribute ) VoxelizerNode.attributeAffects( VoxelizerNode.voxelDistanceAttribute, VoxelizerNode.outputMeshAttribute ) VoxelizerNode.attributeAffects( VoxelizerNode.inputMeshAttribute, VoxelizerNode.outputMeshAttribute ) def initializePlugin( mobject ): ''' Initialize the plug-in ''' mplugin = OpenMaya.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator, nodeInitializer ) except: sys.stderr.write( 'Failed to register node: ' + kPluginNodeName ) raise def uninitializePlugin( mobject ): ''' Uninitializes the plug-in ''' mplugin = OpenMaya.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName ) raise ########################################################## # Sample usage. ########################################################## ''' # Copy the following lines and run them in Maya's Python Script Editor: import maya.cmds as cmds cmds.file( newFile=True, force=True ) cmds.unloadPlugin( 'pyVoxelizerNode.py' ) cmds.loadPlugin( 'pyVoxelizerNode.py' ) # Create a sphere which will act as our input shape. cmds.polySphere( r=5.0, sx=20, sy=20, name='sphere1' ) cmds.move( -20, 0, 0, 'sphere1' ) # move it over to the side. # Create the voxelization node. cmds.createNode( 'voxelizerNode', name='voxelizerNode1' ) # Create a target shape. cmds.createNode( 'transform', name='target1' ) cmds.createNode( 'mesh', name='target1Shape', parent='target1' ) cmds.sets( 'target1Shape', add='initialShadingGroup' ) # Connect the attributes. cmds.connectAttr( 'sphere1Shape.outMesh', 'voxelizerNode1.inputMesh' ) cmds.connectAttr( 'voxelizerNode1.outputMesh', 'target1Shape.inMesh' ) '''
# voxelizerNode.py import sys import maya.OpenMayaMPx as OpenMayaMPx import maya.OpenMaya as OpenMaya # Plug-in information: kPluginNodeName = 'voxelizerNode' # The name of the node. kPluginNodeId = OpenMaya.MTypeId( 0xBEEF6 ) # A unique ID associated to this node type. # Default input values. defaultVoxelWidth = 0.9 # The width of a cubic voxel. defaultVoxelDistance = 1.0 # The distance which separates the center of two adjacent voxels. ########################################################## # Plug-in ########################################################## class VoxelizerNode(OpenMayaMPx.MPxNode): # Static variables which will later be replaced by the node's attributes. voxelWidthAttribute = OpenMaya.MObject() voxelDistanceAttribute = OpenMaya.MObject() inputMeshAttribute = OpenMaya.MObject() outputMeshAttribute = OpenMaya.MObject() def __init__(self): ''' Constructor. ''' # (!) Make sure you call the base class's constructor. OpenMayaMPx.MPxNode.__init__(self) def compute(self, pPlug, pDataBlock): ''' Here, we will create a voxelized version of the input mesh. ''' if( pPlug == VoxelizerNode.outputMeshAttribute ): # Get our custom input node attributes and values. voxelWidthHandle = pDataBlock.inputValue( VoxelizerNode.voxelWidthAttribute ) voxelWidth = voxelWidthHandle.asFloat() voxelDistanceHandle = pDataBlock.inputValue( VoxelizerNode.voxelDistanceAttribute ) voxelDistance = voxelDistanceHandle.asFloat() inputMeshHandle = pDataBlock.inputValue( VoxelizerNode.inputMeshAttribute ) inputMeshObj = inputMeshHandle.asMesh() # Compute the bounding box around the mesh's vertices. boundingBox = self.getBoundingBox( inputMeshObj ) # Determine which voxel centerpoints are contained within the mesh. voxels = self.getVoxels( voxelDistance, inputMeshObj, boundingBox ) # Create a mesh data container, which will store our new voxelized mesh. meshDataFn = OpenMaya.MFnMeshData() newOutputMeshData = meshDataFn.create() # Create a cubic polygon for each voxel and populate the 'newOutputMeshData' MeshData object. self.createVoxelMesh( voxels, voxelWidth, newOutputMeshData) # Set the output data. outputMeshHandle = pDataBlock.outputValue( VoxelizerNode.outputMeshAttribute ) outputMeshHandle.setMObject( newOutputMeshData ) else: return OpenMaya.kUnknownParameter def getBoundingBox(self, pMeshObj): ''' Calculate a bounding box around the mesh vertices. ''' # Create the bounding box object we will populate with the points of the mesh. boundingBox = OpenMaya.MBoundingBox() meshFn = OpenMaya.MFnMesh( pMeshObj ) pointArray = OpenMaya.MPointArray() # Get the points of the mesh in its local coordinate space. meshFn.getPoints( pointArray, OpenMaya.MSpace.kTransform ) for i in range( 0, pointArray.length() ): point = pointArray[i] boundingBox.expand( point ) return boundingBox def getVoxels(self, pVoxelDistance, pMeshObj, pBoundingBox): ''' Obtain a list of voxels as a set of (x,y,z) coordinates in the mesh local space. We obtain these voxels by casting rays from points separated pVoxelDistance apart within the mesh bounding box, and test whether or not these points are contained within the mesh. A point is contained within a closed mesh if the ray shot from the point intersects an odd number of times with the surface of the mesh. ''' # Initialize a list of voxels contained within the mesh. voxels = [] # Get a reference to the MFnMesh function set, and use it on the given mesh object. meshFn = OpenMaya.MFnMesh( pMeshObj ) # Compute an offset which we will apply to the min and max corners of the bounding box. halfVoxelDist = 0.5 * pVoxelDistance # Offset the position of the minimum point to account for the inter-voxel distance. minPoint = pBoundingBox.min() minPoint.x += halfVoxelDist minPoint.y += halfVoxelDist minPoint.z += halfVoxelDist # Offset the position of the maximum point to account for the inter-voxel distance. maxPoint = pBoundingBox.max() maxPoint.x += halfVoxelDist maxPoint.y += halfVoxelDist maxPoint.z += halfVoxelDist # Define an iterator which will allow us to step through the pVoxelDistance # point intervals contained within our bounding box. We use this iterator # in the for loops that follow to visit each voxel center in the bounding box. def floatIterator(start, stop, step): r = start while r < stop: yield r r += step # Iterate over every point in the bounding box, stepping by pVoxelDistance... for xCoord in floatIterator( minPoint.x, maxPoint.x, pVoxelDistance ): for yCoord in floatIterator( minPoint.y, maxPoint.y, pVoxelDistance ): for zCoord in floatIterator( minPoint.z, maxPoint.z, pVoxelDistance ): # 2D representation of a ray cast from the point within the bounding box: # # (+) ^----------------- # | | # y | | - We are shooting the ray from the point: [*] # axis | <======[*] | - The direction of the ray is parallel to the -Z axis. # | | # | | # (-) ------------------> # (-) z axis (+) # # If the ray intersects with an odd number of points along the surface of the mesh, the # point is contained within the mesh (assuming a closed mesh). raySource = OpenMaya.MFloatPoint( xCoord, yCoord, zCoord ) rayDirection = OpenMaya.MFloatVector( 0, 0, -1 ) intersectionPoints = OpenMaya.MFloatPointArray() tolerance = 0.0001 meshFn.allIntersections( raySource, # raySource - where we are shooting the ray from. rayDirection, # rayDirection - the direction in which we are shooting the ray. None, # faceIds - here, we do not care if specific faces are intersected) None, # triIds - here, we do not care if specific tri's are intersected) False, # idsSorted - here, we do not need to sort the faceId's or triId's indices. OpenMaya.MSpace.kTransform, # coordinate space - the mesh's local coordinate space. float(9999), # the range of the ray. False, # testBothDirections - we are not checking both directions from the raySource None, # accelParams - this object is not applicable here. False, # sortHits - we do not need to sort the intersection points along the ray. intersectionPoints, # hitPoints - the array of points which have been intersected. None, # hitRayParams - we do not need any parametric distances of the points along the ray. None, # hitFaces - we do not need the id's of the faces intersected. None, # hitTriangles - we do not need the id's of the triangles intersected. None, # hitBary1s - we do not need the barycentric coordinates of the points within the triangles. None, # hitBary2s - we do not need the barycentric coordinates of the points within the triangles. tolerance # tolerance - a numeric tolerance threshold which allow intersections to occur just outside the mesh. ) # If there is an odd number of intersection points, then the point lies within the mesh. Otherwise, # the point lies outside the mesh. We are only concerned with voxels whose centerpoint lies within the mesh if( intersectionPoints.length() % 2 == 1 ): voxels.append( raySource ) # Return the list of voxel coordinates which lie within the mesh. return voxels def createVoxelMesh(self, pVoxelPositions, pVoxelWidth, pOutMeshData): ''' Create a mesh containing one cubic polygon for each voxel in the pVoxelPositions list. ''' numVoxels = len( pVoxelPositions ) numVerticesPerVoxel = 8 # a cube has eight vertices. numPolygonsPerVoxel = 6 # a cube has six faces. numVerticesPerPolygon = 4 # four vertices are required to define a face of a cube. numPolygonConnectsPerVoxel = numPolygonsPerVoxel * numVerticesPerPolygon # 24 # Initialize the required arrays used to create the mesh in MFnMesh.create() totalVertices = numVoxels * numVerticesPerVoxel vertexArray = OpenMaya.MFloatPointArray() vertexArray.setLength( totalVertices ) vertexIndexOffset = 0 totalPolygons = numVoxels * numPolygonsPerVoxel polygonCounts = OpenMaya.MIntArray() polygonCounts.setLength( totalPolygons ) polygonCountsIndexOffset = 0 totalPolygonConnects = numVoxels * numPolygonConnectsPerVoxel polygonConnects = OpenMaya.MIntArray() polygonConnects.setLength( totalPolygonConnects ) polygonConnectsIndexOffset = 0 # Populate the required arrays used in MFnMesh.create() for i in range( 0, numVoxels ): voxelPosition = pVoxelPositions[i] # Add a new cube to the arrays. self.createCube( voxelPosition, pVoxelWidth, vertexArray, vertexIndexOffset, numVerticesPerVoxel, polygonCounts, polygonCountsIndexOffset, numPolygonsPerVoxel, numVerticesPerPolygon, polygonConnects, polygonConnectsIndexOffset ) # Increment the respective index offsets. vertexIndexOffset += numVerticesPerVoxel polygonCountsIndexOffset += numPolygonsPerVoxel polygonConnectsIndexOffset += numPolygonConnectsPerVoxel # Create the mesh now that the arrays have been populated. The mesh is stored in pOutMeshData meshFn = OpenMaya.MFnMesh() meshFn.create( totalVertices, totalPolygons, vertexArray, polygonCounts, polygonConnects, pOutMeshData ) def createCube(self, pVoxelPosition, pWidth, pVertexArray, pVertexIndexOffset, pNumVerticesPerVoxel, pPolygonCountArray, pPolygonCountIndexOffset, pNumPolygonsPerVoxel, pNumVerticesPerPolygon, pPolygonConnectsArray, pPolygonConnectsIndexOffset ): ''' Add a cubic polygon to the specified arrays. ''' # We are using half the given width to compute the vertices of the cube. halfWidth = float( pWidth / 2.0 ) # Declare the eight corners of the cube. The cube is centered at pVoxelPosition. vertices = [OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 0 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 1 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 2 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, -halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 3 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z), # 4 OpenMaya.MFloatPoint(-halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 5 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, halfWidth + pVoxelPosition.z), # 6 OpenMaya.MFloatPoint( halfWidth + pVoxelPosition.x, halfWidth + pVoxelPosition.y, -halfWidth + pVoxelPosition.z)] # 7 # Declare the data structure which binds each vertex to a polygon corner polygonConnections = [ (0, 12, 16), # the vertex indexed at 0 corresponds to the polygon corners whose indexes are (0, 12, 16) in pPolygonConnectsArray. (1, 19, 20), (2, 9, 23), (3, 8, 13), (4, 15, 17), (5, 11, 14), (6, 10, 22), (7, 18, 21) ] # Store the eight corners of the cube in the vertex array. for i in range( 0, pNumVerticesPerVoxel ): # Store the vertex in the passed vertex array. pVertexArray.set( vertices[i], pVertexIndexOffset + i ) # Assign the vertex in the pVertexArray to the relevant polygons. for polygonConnectionIndex in polygonConnections[i]: pPolygonConnectsArray.set( pVertexIndexOffset + i, pPolygonConnectsIndexOffset + polygonConnectionIndex ) # Declare the number of vertices for each face. for i in range( 0, pNumPolygonsPerVoxel ): # Set the number of vertices for the polygon at the given index. pPolygonCountArray.set( pNumVerticesPerPolygon, pPolygonCountIndexOffset + i ) ########################################################## # Plug-in initialization. ########################################################## def nodeCreator(): ''' Creates an instance of our node class and delivers it to Maya as a pointer. ''' return OpenMayaMPx.asMPxPtr( VoxelizerNode() ) def nodeInitializer(): ''' Defines the input and output attributes as static variables in our plug-in class. ''' # The following MFnNumericAttribute function set will allow us to create our attributes. numericAttributeFn = OpenMaya.MFnNumericAttribute() # This one allows us to create our input and output mesh attributes. typedAttributeFn = OpenMaya.MFnTypedAttribute() #================================== # INPUT NODE ATTRIBUTE(S) #================================== # We will need a voxel width. global defaultVoxelWidth VoxelizerNode.voxelWidthAttribute = numericAttributeFn.create( 'voxelWidth', 'vw', OpenMaya.MFnNumericData.kFloat, defaultVoxelWidth ) numericAttributeFn.setWritable( True ) numericAttributeFn.setReadable( False ) numericAttributeFn.setStorable( True ) numericAttributeFn.setHidden( False ) numericAttributeFn.setMin( 0.1 ) VoxelizerNode.addAttribute( VoxelizerNode.voxelWidthAttribute ) # We will need a voxel distance value (higher values means we can see more voxels within the volume of the mesh). global defaultVoxelDistance VoxelizerNode.voxelDistanceAttribute = numericAttributeFn.create( 'voxelDistance', 'vd', OpenMaya.MFnNumericData.kFloat, defaultVoxelDistance ) numericAttributeFn.setWritable( True ) numericAttributeFn.setReadable( False ) numericAttributeFn.setStorable( True ) numericAttributeFn.setHidden( False ) numericAttributeFn.setMin( 0.1 ) VoxelizerNode.addAttribute( VoxelizerNode.voxelDistanceAttribute ) # We will need an input mesh attribute. VoxelizerNode.inputMeshAttribute = typedAttributeFn.create( 'inputMesh', 'im', OpenMaya.MFnData.kMesh ) typedAttributeFn.setWritable( True ) typedAttributeFn.setReadable( False ) typedAttributeFn.setStorable( False ) typedAttributeFn.setHidden( False ) VoxelizerNode.addAttribute( VoxelizerNode.inputMeshAttribute ) #================================== # OUTPUT NODE ATTRIBUTE(S) #================================== VoxelizerNode.outputMeshAttribute = typedAttributeFn.create( 'outputMesh', 'om', OpenMaya.MFnData.kMesh ) typedAttributeFn.setWritable( False ) typedAttributeFn.setReadable( True ) typedAttributeFn.setStorable( False ) typedAttributeFn.setHidden( False ) VoxelizerNode.addAttribute( VoxelizerNode.outputMeshAttribute ) #================================== # NODE ATTRIBUTE DEPENDENCIES #================================== # If any of the inputs change, the output mesh will be recomputed. VoxelizerNode.attributeAffects( VoxelizerNode.voxelWidthAttribute, VoxelizerNode.outputMeshAttribute ) VoxelizerNode.attributeAffects( VoxelizerNode.voxelDistanceAttribute, VoxelizerNode.outputMeshAttribute ) VoxelizerNode.attributeAffects( VoxelizerNode.inputMeshAttribute, VoxelizerNode.outputMeshAttribute ) def initializePlugin( mobject ): ''' Initialize the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeName, kPluginNodeId, nodeCreator, nodeInitializer ) except: sys.stderr.write( 'Failed to register node: ' + kPluginNodeName ) raise def uninitializePlugin( mobject ): ''' Uninitializes the plug-in ''' mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( 'Failed to deregister node: ' + kPluginNodeName ) raise ########################################################## # Sample usage. ########################################################## ''' # Copy the following lines and run them in Maya's Python Script Editor: import maya.cmds as cmds cmds.file( newFile=True, force=True ) cmds.unloadPlugin( 'voxelizerNode.py' ) cmds.loadPlugin( 'voxelizerNode.py' ) # Create a sphere which will act as our input shape. cmds.polySphere( r=5.0, sx=20, sy=20, name='sphere1' ) cmds.move( -20, 0, 0, 'sphere1' ) # move it over to the side. # Create the voxelization node. cmds.createNode( 'voxelizerNode', name='voxelizerNode1' ) # Create a target shape. cmds.createNode( 'transform', name='target1' ) cmds.createNode( 'mesh', name='target1Shape', parent='target1' ) cmds.sets( 'target1Shape', add='initialShadingGroup' ) # Connect the attributes. cmds.connectAttr( 'sphere1Shape.outMesh', 'voxelizerNode1.inputMesh' ) cmds.connectAttr( 'voxelizerNode1.outputMesh', 'target1Shape.inMesh' ) '''