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)