Maya Frustum Visualizer

Electrons

Why Maya doesn’t offer this functionality out of the box is a mystery to me. I suspect there is actually a way to do it but it’s so buried I can’t find it. I needed it for a recent job so I’m putting it out there for public consumption. Anyway, it’s simple: just select a camera and run the script to visualize the frustum. It should update if you change the camera transforms, film back and FOV. Use it, change it, break it, fix it, improve it at your leisure…

 """A Maya Python script that builds frustum geometry based on the selected camera. Thomas Hollier 2015. hollierthomas@gmail.com""" 
import maya.cmds as cmds
import math, sys

#--------- Gather relevant camera attributes
import maya.cmds as cmds
import math, sys

#--------- Gather relevant camera attributes
camera = cmds.ls(selection=True)

if not cmds.objectType(camera, isType="camera"):
	print "ERROR: You need to select a camera."
	sys.exit(0)

focalLength = cmds.getAttr(camera[0]+".focalLength")
horizontalAperture = cmds.getAttr(camera[0]+".cameraAperture")[0][0]
verticalAperture = cmds.getAttr(camera[0]+".cameraAperture")[0][1]
nearClipping = cmds.getAttr(camera[0]+".nearClipPlane")
farClipping = cmds.getAttr(camera[0]+".farClipPlane")

print "---- Camera Attributes:\n\tfocal length: %s\n\thorizontal aperture: %s" % (focalLength, horizontalAperture)

#--------- compute FOV just for kicks, and to verify numbers match
adjacent = focalLength
opposite = horizontalAperture*.5*25.4

print "---- Right Triangle Values:\n\tadjacent: %s\n\topposite: %s" % (adjacent, opposite)

horizontalFOV = math.degrees(math.atan(opposite/adjacent))*2

print "\tcomputed horizontal FOV: %s" % (horizontalFOV)

#--------- calculate ratios
plane = horizontalAperture*25.4
nearScaleValue = nearClipping*plane/focalLength
farScaleValue = farClipping*plane/focalLength

print "---- Lens:\n\tprojection ratio: %s" % (plane/focalLength)

#--------- build geometry
myCube = cmds.polyCube(w=1, h=1, d=farClipping-nearClipping, sy=1, sx=1, sz=1, ax=[0,1,0], ch=1, name=camera[0].replace("Shape", "Frustrum"))
 
cmds.setAttr(myCube[0]+".translateZ", nearClipping+(farClipping-nearClipping)*.5)
cmds.makeIdentity(apply=True, t=1, r=1, s=1, n=0, pn=1);
cmds.setAttr(myCube[0]+".rotatePivotZ", 0)
cmds.setAttr(myCube[0]+".scalePivotZ", 0)
cmds.setAttr(myCube[0]+".rotateY", 180)

#--------- use expressions to update frustum geo as FOV and apertures are changed 
scaleX = "%s.scaleZ*%s.farClipPlane*%s.horizontalFilmAperture*25.4/%s.focalLength" % (myCube[0],camera[0],camera[0],camera[0])
scaleY = "%s.scaleZ*%s.farClipPlane*%s.verticalFilmAperture*25.4/%s.focalLength" % (myCube[0],camera[0],camera[0],camera[0])

cmds.move(0,0,0, myCube[0]+".f[2]", absolute=True)
cmds.scale(nearScaleValue, 0, 1, myCube[0]+".f[2]", pivot=[0,0,0])
cmds.expression(s="%s.scaleX = %s;%s.scaleY = %s;" % (myCube[0],scaleX,myCube[0],scaleY), n="%s_Expr" % myCube[0])
cmds.parent(myCube, camera, relative=True)