Voxelize Meshes Script v.2

February 25th, 2010

With the help of the patient people in the python_inside_maya forum, I’ve improved the Voxelize Meshes Script, mostly by more efficient use of the allIntersections method.

Instead of checking each point on the grid to see whether it’s inside one of the target meshes, this version shoots rays through the meshes along each axis and puts blocks at the intersections. This makes it approximately a zillion times faster, though I’m sure it could still be improved.

Update: Richard Kazuo from the p_i_m forum has excised lingering traces of pymel from my script, I’ve updated the code below with his improved version. It should now run with Maya’s default Python installation. Thanks Richard!

Update 2: Here’s the even-more-efficient voxelize_meshes_v.3.py … I’m putting this to bed now.

Maya Python code:

### start: voxelize meshes v.2.5
### this script turns the selected meshes into cubes
### over the current timeline range.

import maya.OpenMaya as om
import maya.cmds as cmds
import maya.mel as mel

# shoot a ray from point in direction and return all hits with mesh
def rayIntersect(mesh, point, direction=(0.0, 0.0, -1.0)):
  # get dag path of mesh - so obnoxious
  om.MGlobal.clearSelectionList()
  om.MGlobal.selectByName(mesh)
  sList = om.MSelectionList()
  om.MGlobal.getActiveSelectionList(sList)
  item = om.MDagPath()
  sList.getDagPath(0, item)
  item.extendToShape()
  fnMesh = om.MFnMesh(item)

  raySource = om.MFloatPoint(point[0], point[1], point[2], 1.0)
  rayDir = om.MFloatVector(direction[0], direction[1], direction[2])
  faceIds            = None
  triIds             = None
  idsSorted          = False
  worldSpace         = om.MSpace.kWorld
  maxParam           = 99999999
  testBothDirections = False
  accelParams        = None
  sortHits           = True
  hitPoints          = om.MFloatPointArray()
  hitRayParams       = om.MFloatArray()
  hitFaces           = om.MIntArray()
  hitTris            = None
  hitBarys1          = None
  hitBarys2          = None
  tolerance          = 0.0001

  hit = fnMesh.allIntersections(raySource, rayDir, faceIds, triIds, idsSorted, worldSpace, maxParam, testBothDirections, accelParams, sortHits, hitPoints, hitRayParams, hitFaces, hitTris, hitBarys1, hitBarys2, tolerance)

  # clear selection as may cause problems if called repeatedly
  om.MGlobal.clearSelectionList()
  result = []
  for x in range(hitPoints.length()):
    result.append((hitPoints[x][0], hitPoints[x][1], hitPoints[x][2]))
  return result

# round to nearest fraction in decimal form: 1, .5, .25
def roundToFraction(input, fraction):
  factor = 1/fraction
  return round(input*factor)/factor

# progress bar, enabling "Esc"
def makeProgBar(length):
  global gMainProgressBar
  gMainProgressBar = mel.eval('$tmp = $gMainProgressBar');
  cmds.progressBar( gMainProgressBar,
        edit=True,
        beginProgress=True,
        isInterruptable=True,
        maxValue=length
        )

def promptNumber():
  result = cmds.promptDialog(
      title='Grow Shrub',
      message='Block size:',
      text="1",
      button=['OK', 'Cancel'],
      defaultButton='OK',
      cancelButton='Cancel',
      dismissString='Cancel')
  if result == 'OK':
    return float(cmds.promptDialog(query=True, text=True))
  else: return 0

def init(cubeSize):
  global cubeDict, xLocs, yLocs, zLocs
  cubeDict = {}
  xLocs = []
  yLocs = []
  zLocs = []

  # make 3 arrays of ray start points, one for each axis
  # this is necessary because rays aren't likely to catch surfaces
  # which are edge-on... so to make sure to catch all faces,
  # we shoot rays along each axis
  fac = 1/cubeSize
  for y in range(ymin*fac, ymax*fac+1):
    for z in range(zmin*fac, zmax*fac+1):
      loc = (xmax, y*cubeSize, z*cubeSize)
      xLocs.append(loc)
  for z in range(zmin*fac, zmax*fac+1):
    for x in range(xmin*fac, xmax*fac+1):
      loc = (x*cubeSize, ymax, z*cubeSize)
      yLocs.append(loc)
  for x in range(xmin*fac, xmax*fac+1):
    for y in range(ymin*fac, ymax*fac+1):
      loc = (x*cubeSize, y*cubeSize, zmax)
      zLocs.append(loc)

# start the action
if len(cmds.ls(sl=1)) == 0:
  result = cmds.confirmDialog( title='Mesh selection', message= 'Please select a mesh.', button=['OK'])
else:
  startTime= cmds.timerX()
  cubeSize = promptNumber()

  # set selected objects to be the shape targets, aka controls
  ctrl = cmds.ls(sl=1)

  firstFrame = int(cmds.playbackOptions(query=1, min=1))
  lastFrame = int(cmds.playbackOptions(query=1, max=1))
  duration = int(lastFrame-firstFrame)

  makeProgBar(duration*len(ctrl))
  cmds.progressBar(gMainProgressBar, edit=True, beginProgress=1)

  bb = cmds.exactWorldBoundingBox(ctrl[0])
  xmin = bb[0]
  xmax = bb[3]
  xdist = abs(xmin)+abs(xmax)
  ymin = bb[1]
  ymax = bb[4]
  ydist = abs(ymin)+abs(ymax)
  zmin = bb[2]
  zmax = bb[5]

  print "Finding bounding box of animation..."
  print "Press ESC to cancel"
  # find outer boundaries of animation
  for f in range(firstFrame,lastFrame):
    for c in ctrl:
      if cmds.progressBar(gMainProgressBar, query=1, isCancelled=1 ):
        break
      cmds.currentTime(f)
      cmds.progressBar(gMainProgressBar, edit=1, step=1)

      bb = cmds.exactWorldBoundingBox(c)
      xmin = min(xmin, bb[0])
      xmax = max(xmax, bb[3])
      ymin = min(ymin, bb[1])
      ymax = max(ymax, bb[4])
      zmin = min(zmin, bb[2])
      zmax = max(zmax, bb[5])

  cmds.progressBar(gMainProgressBar, edit=1, endProgress=1)

  init(cubeSize)

  cmds.progressBar(gMainProgressBar, edit=1, endProgress=1)
  makeProgBar(duration*len(ctrl))
  cmds.progressBar(gMainProgressBar, edit=1, beginProgress=1)

  print "Animating visibility over", duration, "frames..."
  print "Press ESC to cancel"

  # animate cube visibility
  resetList = []
  for f in range(firstFrame,lastFrame+1): # for each frame
    cmds.currentTime(f, edit=1, update=1)
    # if the cube was visible last frame, hide it
    for x in resetList:
      cmds.setKeyframe(x, at="scale", v=0, t=f)
    resetList = []
    locArrays = [xLocs, yLocs, zLocs]
    directions = [(-1.0, 0.0, 0,0), (0.0, -1.0, 0,0), (0.0, 0.0, -1.0)]
    # for every target control object:
    for c in ctrl:
      if cmds.progressBar(gMainProgressBar, query=1, isCancelled=1 ):
        break
      cmds.progressBar(gMainProgressBar, edit=1, step=1)
      # for each axis:
      for i in range(3):
        cmds.flushUndo()
        # for every gridpoint orthagonal to the animation:
        for loc in locArrays[i]:
          hits = []
          # zap a ray thrugh the object
          hits = rayIntersect(c, loc, directions[i])
          for x in hits:
            # snap hit locations to cubegrid
            x = (roundToFraction(x[0], cubeSize), roundToFraction(x[1], cubeSize), roundToFraction(x[2], cubeSize) )
            # if location isn't in cubeDict, add it and a new cube
            if x not in cubeDict:
              cubeDict[x] = cmds.polyCube(sz=1, sy=1, sx=1, cuv=4, d=cubeSize, h=cubeSize, w=cubeSize, ch=1)[0]
              # move cube to quantized location
              cmds.xform(cubeDict[x], t=x)
            # set a scale key
            cmds.setKeyframe(cubeDict[x], at="scale", v=1, t=f)
            # if previous frame didn't have a scale key, set it to 0
            tempCurTime = cmds.currentTime(q=1)-1
            lastKey = cmds.keyframe(cubeDict[x], at="scale", q=1, t=(tempCurTime,tempCurTime), valueChange=1)
            if lastKey == None or lastKey[0] != 1.0:
              cmds.setKeyframe(cubeDict[x], at="scale", v=0, t=(f-1))
            # add cube to resetList
            resetList.append(cubeDict[x])
    cmds.currentTime(f, edit=1, update=1)

  cmds.progressBar(gMainProgressBar, edit=1, endProgress=1)
  totalTime = cmds.timerX(startTime=startTime)
  print("Total Time: "+str(totalTime))

### end voxelize meshes v.2.5
« previously: Voxelize Meshes Script | Home | next: Patrick Jean – Pixels »

18 Responses to “Voxelize Meshes Script v.2”

  1. Peter P Says:

    I looked at your code and it seems to be generating cubes every frame positioning them and animating visibility. Is it possible to reduce the number of cubes needed and reuse old cubes from previous frames through keyframing new positions for the old cubes? and perhaps calculate if extra cubes will be needed on more complex angles?

  2. zoomy Says:

    Hey Peter,

    It should be reusing cubes — every time a new cube is made, it’s added to cubeDict, keyed by its location; then, when a new location for a cube is identified, cubeDict is checked to see if there’s already a cube at that spot; that’s the “if x not in cubeDict” line.

    I’m not sure I follow your suggestion about complex angles — the code doesn’t do any checks for complexity of the model at all, it just puts a cube wherever a ray happens to hit something. Are you talking about using larger cubes for bigger masses, and smaller cubes to fill in detail? That would be another swimming pool of worms entirely.

  3. Peter P Says:

    Right now its working as a large intersection matrix cube. Wherever there is intersection a cube is made and if the object goes back to that old position the position is called from the dictionary and reused.

    The problem with that method is if you have a large mesh or long animation frame count covering large distance your poly count goes up very quickly.

    I was recommending on reusing old hiden cubes by means of translating them into new positions every frame. That way you could reuse a lot of old cubes and only rely on keyframes instead of poly count.

  4. zoomy Says:

    I see what you mean — there are a few things I like about having persistent cubes, but your recommendation would make the final scenes much lighter. Maybe I’ll do a variant someday…

  5. anthony mcgrath Says:

    hi there
    I’ve just been playing with your script a little and I think its superb! I created a simple 3 frame animation, ran the script and got the result I wanted excellent. however I then decided to group the meshes per frame to tidy my scene up which I was happy to do manually (for the sake of 3 frames lol). This is where things got strange for me as a maya user.. You are setting keys on the scale from 1 to 0 so when you drag over the cubes in the scene that are meant to be on the NEXT frame in order to group them up, you end up selecting them on the other frames also.

    A simple workaround for this was to edit your script so anywhere it sets an attribute value for scale I simply changed that attribute to visibility instead. It works a treat and when you group those cubes per frame manually you now no longer select the meshes on the other frames :)
    A natty option would be for the script to give the option to combine all the cubes on each frame into one mesh node or if you dont select that option it simply groups all the cubes under an appropriately named group node eg. frame0_GRP, frame1_GRP, frame2_GRP etc..

    hope that helps and makes sense mate (typing it quite tired lol!) mate – awesome script :)
    ant

  6. zoomy Says:

    Thanks Anthony, good suggestions — theoretically I could make all of these options available, so the user could choose the option that best suits their needs.

  7. anthony mcgrath Says:

    cool mate – cant wait to see an update :)

  8. Somkiat Says:

    Hi ,
    sorry for my Englsih …cause I’m french .
    I ‘ll try Your script last day , but It doesn’t work on Maya 2010 and Maya 2009
    This is a bug report :

    ### end voxelize meshes v.3;
    // Error: ### voxelize meshes v.3
    //
    // Error: invalid directive //
    // Error: cmds.progressBar( gMainProgressBar,
    //
    // Error: Invalid use of Maya object “gMainProgressBar”. //
    // Error: edit=True,
    //
    // Error: Invalid use of Maya object “True”. //
    // Error: beginProgress=True,
    //
    // Error: Invalid use of Maya object “True”. //
    // Error: isInterruptable=True,
    //
    // Error: Invalid use of Maya object “True”. //
    // Error: )
    //
    // Error: Invalid use of Maya object “length”. //

    Have you got a video tutorial for using this amazing script .

    Thanks you a lot and good working !

    Somkiat

  9. Somkiat Says:

    Hello everyone!
    I just found my mistake , I put the script on the Mel tab …or the best way it was put in on
    Python Tab. All be fine !
    I currently directed a short movie clip . Can I use your scipt . It’s not for commercial used …just student movie.

    In the End I put the name and tool on the ending title .

  10. Jimmy Gunawan Says:

    Very neat little script. I stumbled upon your script while I was curious on how they accomplised this effect:
    http://www.itsartmag.com/forum/viewtopic.php?f=27&t=3691&utm_source=newsletter&utm_medium=cpm&utm_content=video&utm_campaign=newsletter070410

    Perhaps a similar concept, but in that clip, they can:
    1. Resize the voxel cube size in real time?
    2. Take colors from the original object’s surface/texture and put it into the voxel.

    Very nice script, I like it a lot! Thanks.

  11. Jimmy Gunawan Says:

    Btw, guys, do any of you experience memory crash using Maya Batch, when we use this script to create over 4000-8000 or more cubes that simply scale 0-1? This is a little bit ridiculous, I don’t get it. I could render any frame, fine, but not during Batch.

    I am using Maya 2010, 64 bit. I seriously, could batch render a really complex scene, but not this scene, for unknown reason. It’s definitely a bug.

    I tried Maya Software, Renderman for Maya, Mental Ray for Maya. All crashed, batch just stopped without any explanation. The issue is with Maya Batch.

    Would it be possible to use thousands of cube Instance for this script, instead of normal cube? Also, maybe the Scale has issue, Visibility maybe better?

  12. zoomy Says:

    @Somkiat – Yes, you can use it any way you like. Thank you for the attribution!

    @Jimmy – I haven’t had any batch render trouble. Instancing could be better in some cases. Re-sizing the cubes on the fly would be an interesting effect… and getting color information is possible too, I hope to have an update sometime soon!

  13. forcefieldmaker Says:

    Hey I’m trying to figure out how to get this too work I get the same thing as somkiat did but I am in the python tab in the script editor. Any help? Im just starting script usage in Maya 2011.

  14. zoomy Says:

    I’d suggest starting with a simpler script. Good luck!

  15. forcefieldmaker Says:

    Hey thanks!
    Definitely feel a lil outta my element but
    Just real quick though, does it work with Maya 2011?

  16. zoomy Says:

    I haven’t heard anything to the contrary.

  17. Anora Says:

    color information would be great

  18. Jaewon Song Says:

    I made voxelization tool with this script. Thank you for your advise.

    If anyone want to download my voxelization tool, you can download at

    http://cimple.tistory.com/394

    This blog written in Korean, but you can find easily the python script.

Leave a Reply