here’s an improved version, it allows to select the camera angle to render from.
--*************************************************************************************
--* Object Contact Sheet
--* By Ofer Zelichover
--* www.oferz.com
--*
--* Created for a CGTalk MaxScript Challenge. 21/7/2005
--*************************************************************************************
try (destroyDialog ro_ContactSheet) catch()
rollout ro_ContactSheet "Object Contact Sheet"
(
-- Local Variable declerations
--------------------------------------------------------------
local indent = ""
local HTMLFilename = ""
-- User Interface
--------------------------------------------------------------
group "Output Folder: " (
radioButtons rbOutputFolder "" labels:#("Use the current max file folder.", "Use a custom folder:") align:#left columns:1 default:(if maxFilePath != "" then 1 else 2)
edittext edOutputFolder "" fieldWidth:270 align:#left across:2 offset:[-8,0]
button bnOutputFolderBrowse "..." width:18 height:16 align:#right offset:[3,0]
)
group "Render Options: " (
spinner spOutputWidth "Output Size: Width:" fieldWidth:45 type:#integer range:[1, 9e5, renderWidth] align:#left across:2
spinner spOutputHeight "Height:" fieldWidth:45 type:#integer range:[1, 9e5, renderHeight] align:#left offset:[20,0]
label lblImageType "Image Type: " align:#left across:2 offset:[0,3]
dropDownList ddlImageType "" width:50 items:#("jpg", "png") default:1 align:#left offset:[-80,0]
colorPicker cpBGColor "Background Color: " height:18 fieldWidth:25 align:#right offset:[0,-26] color:backgroundColor
spinner spCameraFOV "Camera FOV: " fieldWidth:40 range:[1.0, 179., 30.] type:#float align:#left offset:[0,3]
radioButtons rbCameraAngle "Camera Angle:" labels:#("Front", "Top", "Side", "Perspective", "All") default:1 align:#left columns:3 offset:[0,3]
radioButtons rbLights "Lights:" labels:#("Use Scene Lights", "Use Internal Light Rig") default:2 align:#left columns:1 offset:[0,3]
)
group "Zip Files: " (
checkbox cbEnableZip "Zip files into file:" align:#left
edittext edZipFile "" fieldWidth:250 align:#left across:2 offset:[12,0] enabled:cbEnableZip.checked
button bnZipFileBrowse "..." width:18 height:16 align:#right offset:[3,0] enabled:cbEnableZip.checked
checkbox cbZipDeleteFiles "Delete contact sheet files after zipping." align:#left enabled:cbEnableZip.checked
button bnZipOpenFolder "Open Zip File Folder" width:290 height:20 enabled:(doesFileExist edZipFile.text)
)
group "Progress: " (
label lblProgTotal "Processed/Total number of objects: " align:#left
label lblProgExported "Actual number of exported objects: " align:#left
progressBar pbProgress ""
)
button bnGo "Create Contact Sheet" width:300 height:25 enabled:(rbOutputFolder.state == 1)
button bnView "View Contact Sheet" width:145 height:25 enabled:false align:#left across:2 offset:[-8,0]
button bnOpenFolder "Open HTML Folder" width:145 height:25 enabled:false align:#right offset:[8,0]
-- General Functions
--------------------------------------------------------------
fn getVisibleObjects =
(
for o in objects where not o.isHidden collect o
)
fn getEnabledLights =
(
for l in lights where (not isKindOf l TargetObject and l.baseObject.on) collect l
)
fn getNumVerts obj =
(
obj.mesh.verts.count
)
fn getNumFaces obj =
(
try (
obj.faces.count
) catch (
obj.mesh.faces.count
)
)
fn getObjectTextueFiles obj =
(
local tex = #()
if obj != undefined then (
for i = 1 to obj.numSubs do (
if isKindOf obj[i] Bitmaptexture then
append tex obj[i].filename
join tex (getObjectTextueFiles obj[i])
)
)
tex
)
-- HTML Writing Functions
--------------------------------------------------------------
fn incIndent &i = i += " "
fn decIndent &i =
(
if i.count > 0 then
i = subString i 1 (i.count - 1)
else
""
)
fn writeObjTexturesHTML HTMLFileHandle obj photoName &indent =
(
local tex = getObjectTextueFiles obj.material
if tex.count > 0 then (
incIndent &indent
format "%<b>Texture Files:</b><br />
" indent to:HTMLFileHandle
format "%<ul>
" indent to:HTMLFileHandle
incIndent &indent
for t in tex do
format "%<li>%</li>
" indent t to:HTMLFileHandle
decIndent &indent
format "%</ul>
" indent to:HTMLFileHandle
decIndent &indent
)
)
fn writeObjHTMLCode HTMLFileHandle obj photoName &indent =
(
try (
format "%<tr>
" indent to:HTMLFileHandle
incIndent &indent
format "%<td>
" indent to:HTMLFileHandle
incIndent &indent
format "%<img src=\"%\" style=\"border: 1px solid black; display: inline;\" alt=\"%\" />
" indent photoName obj.name to:HTMLFileHandle
decIndent &indent
format "%</td>
" indent to:HTMLFileHandle
format "%<td valign=\"top\" align=\"left\" width=\"100\%\">
" indent to:HTMLFileHandle
incIndent &indent
format "%<b>Object Name:</b>%<br />
" indent obj.name to:HTMLFileHandle
format "%<b>Vertices:</b>%<br />
" indent (getNumVerts obj) to:HTMLFileHandle
format "%<b>Faces:</b>%<br />
" indent (getNumFaces obj) to:HTMLFileHandle
writeObjTexturesHTML HTMLFileHandle obj photoName &indent
decIndent &indent
format "%</td>
" indent to:HTMLFileHandle
decIndent &indent
format "%</tr>
" indent to:HTMLFileHandle
-- create a divider
format "%<tr>
" indent to:HTMLFileHandle
incIndent &indent
format "%<td colspan=\"2\">
" indent to:HTMLFileHandle
incIndent &indent
format "%<hr />
" indent photoName obj.name to:HTMLFileHandle
decIndent &indent
format "%</td>
" indent to:HTMLFileHandle
decIndent &indent
format "%</tr>
" indent to:HTMLFileHandle
)catch()
)
fn writeHTMLHeaderCode HTMLFileHandle &indent =
(
try (
format "%<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">
" indent to:HTMLFileHandle
format "%<html>
" indent to:HTMLFileHandle
format "%<head>
" indent to:HTMLFileHandle
incIndent &indent
format "%<title>contactSheet - 3DS Max Object Contact Sheet</title>
" indent to:HTMLFileHandle
-- write the styles
format "%<style type=\"text/stylesheet\">
" indent to:HTMLFileHandle
incIndent &indent
format "%hr {
" indent to:HTMLFileHandle
incIndent &indent
format "%height: 1px;
" indent to:HTMLFileHandle
format "%color: Gray;
" indent to:HTMLFileHandle
format "%border-style: solid;
" indent to:HTMLFileHandle
decIndent &indent
format "%}
" indent to:HTMLFileHandle
decIndent &indent
format "%</style>
" indent to:HTMLFileHandle
decIndent &indent
format "%</head>
" indent to:HTMLFileHandle
format "%<body>
" indent to:HTMLFileHandle
incIndent &indent
format "%<table width=\"95\%\" align=\"center\" style=\"border: 2px solid black;\">
" indent to:HTMLFileHandle
incIndent &indent
)catch()
)
fn writeHTMLFooterCode HTMLFileHandle &indent =
(
try (
decIndent &indent
format "%</table>
" indent to:HTMLFileHandle
decIndent &indent
format "%</body>
" indent to:HTMLFileHandle
decIndent &indent
format "%</html>
" indent to:HTMLFileHandle
)catch()
)
-- Render Functions
--------------------------------------------------------------
-- taken form the maxscript help, and slightly modified.
fn GetVFOV fov renderSize:[renderWidth, renderHeight] =
(
local r_aspect=(renderSize.x as float)/renderSize.y
2.0*atan(tan(fov/2.0)/r_aspect)
)
fn genContactSheetCam obj fov renderSize view:#front =
(
-- create the temp camera
local cam = freeCamera name:"ContactSheetCam" fov:fov
-- move and rotate the camera to match the object's transformation
cam.transform = obj.transform
cam.pos = obj.center
local angs = #()
case view of (
#front: angs = #(eulerAngles 90 0 0)
#side: angs = #(eulerAngles 0 90 0, eulerAngles 0 0 90)
#top: angs = #(eulerAngles 0 0 0)
#persp: angs = #(eulerAngles 45 0 45)
default: angs = (eulerAngles 90 0 0)
)
in coordsys cam (
for a in angs do
rotate cam a
)
-- calculate the distance needed to fit the entire object
local bBoxSize = (obj.max - obj.min) * 1.1
local distWidth = (bBoxSize.x / 2.) / tan (fov / 2.)
local distHeight = (bBoxSize.y / 2.) / tan ((getVFOV fov renderSize:renderSize) / 2.)
local dist = amax (abs distWidth) (abs distHeight)
-- move the camera back so it sees the whole object.
in coordsys cam
cam.pos.z += dist
-- return the camera object
cam
)
fn genObjRender obj renderSize fov useSceneLights:false view:#front =
(
-- create the temp camera object.
local camObj = genContactSheetCam obj fov renderSize view:view
-- if we're not going to use the scene lights, create the temp light rig
if not useSceneLights then (
local keyLight = omniLight name:"ContactSheetTempLight" color:white multiplier:1.0
local fillLight = omniLight name:"ContactSheetTempLight" color:white multiplier:0.5
in coordsys camObj (
local dist = distance camObj.pos obj.center
keyLight.pos = [dist, dist, 0]
fillLight.pos = [-dist, -dist, 0]
)
)
-- render the object
local rend = render camera:camObj outputSize:renderSize renderhiddenobjects:false vfb:false
-- delete the camera
delete camObj
-- delete the temp lights
if isValidNode keyLight then
delete keyLight
if isValidNode fillLight then
delete fillLight
-- return the rendered bitmap
rend
)
-- combine 4 images into one image.
fn combineImages images rendSize =
(
-- create a compositeTexture map that will do the compositing
local comp = CompositeTexturemap name:"tempComp"
local filenames = #()
for i = 1 to images.count do (
-- to be able to use a bitmap value as a bitmaptexture the bitmap must first be saved.
-- so, save the bitmap to a temporary file.
images[i].filename = sysInfo.tempDir + "~tempCompBitmap" + i as string + "_" + timeStamp() as string + ".png"
save images[i]
-- append the temp bitmap filename to a list, so it can be deleted later.
append filenames images[i].filename
-- create a bitmapTexture map for the image
local b = Bitmaptexture name:("tempBitmap" + i as string) bitmap:images[i] filtering:1
-- and set some parameters. the compositing is done by setting the UV tiling values to 2
-- so the image is going to be half the size of the final image.
b.coords.U_Mirror = false
b.coords.V_Mirror = false
b.coords.U_Tile = false
b.coords.V_Tile = false
b.coords.U_Tiling = 2.
b.coords.V_Tiling = 2.
b.coords.U_Offset = 0.25 * (if (mod i 2) == 0 then 1 else -1)
b.coords.V_Offset = 0.25 * (if i > 2 then -1 else 1)
-- add the bitmapTexture to the composite map list
comp.mapList[i] = b
)
-- delete the temp files
for f in filenames do
deleteFile f
-- render the composite texture and return the result
rend = renderMap comp size:rendSize display:false
)
fn genMultiRender obj renderSize fov useSceneLights:false =
(
local rendSize = renderSize / 2
local images = #()
-- generate image for each of the 4 views
append images (genObjRender obj rendSize fov useSceneLights:useSceneLights view:#top)
append images (genObjRender obj rendSize fov useSceneLights:useSceneLights view:#front)
append images (genObjRender obj rendSize fov useSceneLights:useSceneLights view:#side)
append images (genObjRender obj rendSize fov useSceneLights:useSceneLights view:#persp)
-- return the combined image
combineImages images renderSize
)
-- contactSheet Functions
--------------------------------------------------------------
-- this function creates a contact sheet entry for a single object.
-- it first renders the object, then generates the HTML code.
fn contactSheetObj obj outputDir HTMLFileHandle renderSize fov useSceneLights:false imgType:"jpg" mode:#render view:#front=
(
-- check if the object is a geometry object, if not quit the fn.
if not (isValidNode obj) or not (isKindOf obj GeometryClass) or (isKindOf obj targetObject) then
return false
-- store the hidden state of the object to restore it later.
local objIsHidded = obj.isHidden
-- make sure the object is not hidden
obj.isHidden = false
local rend = bitmap 30 30 color:black
case mode of (
#render: rend = genObjRender obj renderSize fov useSceneLights:useSceneLights view:view
#multiRender: rend = genMultiRender obj renderSize fov useSceneLights:useSceneLights
) -- end case
-- save the bitmap
local photoName = obj.name + "." + imgType
rend.filename = outputDir + photoName
save rend
writeObjHTMLCode HTMLFileHandle obj photoName &indent
-- restore the object hidden state
obj.isHidden = objIsHidded
-- return true
true
)
-- This function loops through all objects and calls the single object
-- contact sheet to do the actual job.
-- This function also updates the UI, and gets the parameters set in
-- the UI and passes them on.
fn contactSheet =
(
-- declare output name and directory variables.
local outputName = if maxFileName == "" then "Untitled" else (getFilenameFile maxFileName)
local outputDir = if rbOutputFolder.state == 2 then edOutputFolder.text else maxFilePath
if outputDir[outputDir.count] != "\\" and outputDir[outputDir.count] != "/" then
outputDir += "\\"
outputDir += outputName + "\\"
makedir outputDir
local HTMLFileName = outputDir + outputName + ".html"
-- create the HTML file
local HTMLFileHandle = createFile HTMLFileName
-- if the HTML file was not created, abort.
if HTMLFileHandle == undefined then return()
-- Write the HTML headers
writeHTMLHeaderCode HTMLFileHandle &indent
disableSceneRedraw()
-- store the scene state for restoring later.
local renderSize = [spOutputWidth.value, spOutputHeight.value]
local visObjs = getVisibleObjects()
local enabledLights = getEnabledLights()
local BGCol = backgroundColor
backgroundColor = cpBGColor.color
-- turn off scene lights and hide all objects.
if rbLights.state != 1 then (
for l in enabledLights do
l.baseObject.on = off
)
hide objects
-- create the contact sheet elements for each object.
local counter = 0 -- actual number of objects that were processed
local i = 0. -- a counter for the progress bar
local imgType = ddlImageType.selected
local useSceneLights = (rbLights.state == 1)
local view = case rbCameraAngle.state of (1:#front; 2:#top; 3:#side; 4:#persp; 5:#all)
local mode = if rbCameraAngle.state == 5 then #multiRender else #render
local fov = spCameraFOV.value
lblProgTotal.text = "Processed/Total number of objects: 0" + "/" + (objects.count) as string
pbProgress.value = 0.
for o in objects do (
try (
if (contactSheetObj o outputDir HTMLFileHandle renderSize fov useSceneLights:useSceneLights imgType:imgType mode:mode view:view) then
counter += 1
) catch(print ("error (object:" + o.name + ")"))
i += 1.
lblProgTotal.text = "Processed/Total number of objects: " + (i as integer) as string + "/" + (objects.count) as string
pbProgress.value = i / (objects.count as float) * 100.
)
lblProgExported.text = "Actual number of exported objects: " + counter as string
-- write the HTML footers
writeHTMLFooterCode HTMLFileHandle &indent
-- close the HTML file
close HTMLFileHandle
-- restore scene to original state.
for l in enabledLights do
l.baseObject.on = on
unHide visObjs
backgroundColor = BGCol
enableSceneRedraw()
-- return the HTML filename
HTMLFileName
)
-- Zip Functions
-------------------------------------------------------------------
-- archives all the files in dir into zipFilename using maxzip.exe
fn zipFiles dir zipFilename =
(
if zipFilename == "" then
return false
if dir[dir.count] != "\\" and dir[dir.count] != "/" then
dir += "\\"
local files = getFiles (dir + "*")
if not doesFileExist dir or files.count == 0 then
return false
-- generate a file with the list of files to be archived
local listFilename = sysInfo.tempDir + "~maxzipTempList" + timeStamp() as string + ".lst"
local f = createFile listFilename
if f == undefined then
return false
for i in files do
format "%
" i to:f
flush f
close f
-- archive the files into the zip
local curDir = sysInfo.currentDir
local zipUtil = getDir #maxRoot + "maxzip.exe"
local zipFile = getFileNameFile zipFilename + getFileNameType zipFilename
local cmd = "" as stringStream
sysInfo.currentDir = getFilenamePath zipFilename
format "\"%\" % @%" zipUtil zipFile listFilename to:cmd
local archive = dosCommand cmd
sysInfo.currentDir = curDir
-- delete the temporary list file
deleteFile listFilename
-- return true on success false on failure
archive == 0
)
-- deletes all files in a give folder
fn deleteFiles dir =
(
if dir[dir.count] != "\\" and dir[dir.count] != "/" then
dir += "\\"
for f in getFiles (dir + "*") do
deleteFile f
)
-- UI Functions
-------------------------
-- This function is responsible for all the UI elements' update such as enabling/disabling etc.
fn updateUI =
(
local f = if rbOutputFolder.state == 1 then maxFilePath else edOutputFolder.text
if f != "" and f[f.count] != "\\" and f[f.count] != "/" then
f += "\\"
bnGo.enabled = doesFileExist f
edOutputFolder.enabled = rbOutputFolder.state == 2
bnOutputFolderBrowse.enabled = rbOutputFolder.state == 2
edZipFile.enabled = cbEnableZip.checked
bnZipFileBrowse.enabled = cbEnableZip.checked
cbZipDeleteFiles.enabled = cbEnableZip.checked
bnZipOpenFolder.enabled = doesFileExist (getFilenamePath edZipFile.text)
bnView.enabled = doesFileExist HTMLFilename
bnOpenFolder.enabled = doesFileExist (getFilenamePath HTMLFilename)
)
-- Event Handlers
-----------------------------------------------------------------------
on rbOutputFolder changed state do (
if state == 1 then (
if maxFilePath == "" then (
messageBox "It appears the current max file has never been save.
Please save it before using this option."
rbOutputFolder.state = 2
state = 2
)
)
updateUI()
)
on edOutputFolder changed txt do (
updateUI()
)
on bnOutputFolderBrowse pressed do (
local f = getSavePath()
if f != undefined then (
edOutputFolder.text = f
updateUI()
)
)
on cbEnableZip changed state do (
updateUI()
)
on edZipFile changed txt do (
updateUI()
)
on bnZipFileBrowse pressed do (
local f = getSaveFilename filename:(maxFilePath + getFilenameFile maxFileName + ".zip") types:"ZIP Files (*.zip)|*.zip|All Files (*.*)|*.*"
if f != undefined then (
edZipFile.text = f
updateUI()
)
)
on bnZipOpenFolder pressed do (
if doesFileExist (getFilenamePath edZipFile.text) then
shellLaunch (getFilenamePath edZipFile.text) ""
)
on bnGo pressed do (
bnGo.enabled = false
HTMLFilename = contactSheet()
local dir = getFilenamePath HTMLFilename
if cbEnableZip.checked then (
local zipped = zipFiles dir edZipFile.text
if zipped then (
if cbZipDeleteFiles.checked and (queryBox "Delete all files in the contact sheet folder?
This cannot be undone!") then
deleteFiles dir
) else (
messageBox "The zipping of the files failed."
)
)
updateUI()
)
on bnView pressed do (
shellLaunch HTMLFilename ""
)
on bnOpenFolder pressed do (
shellLaunch (getFilenamePath HTMLFilename) ""
)
on ro_ContactSheet open do (
updateUI()
)
)-- end of ro_ContactSheet
createDialog ro_ContactSheet width:310
again, max 7
LOL. I though so too… 
I guess I’m too lazy to do anything about it, and I’m getting a bit bored 
cheers,
o