Notifications
Clear all

[Closed] CgTalk Maxscript Challenge 006: "Object Photo Album"

CgTalk Maxscript Challenge 006: “Object Photo Album”

DESCRIPTION: Create a tool that focuses on each object in a scene and renders each object as a seperate image. For example, if there are three spheres in a scene you would end up with a folder containing three images, one of each object. You can either render the object or viewport snap it.

       INTERMEDIATE SCRIPTERS: Embed the images in a HTML document and add information about each object (poly count, textures etc.)
  
    ADVANCED SCRIPTERS: Compile the whole thing (scene, images and html document) into it's own folder or zip file, ready for "release". Consider this like a tool to keep track of finished max files in a development environment.

          RULES:  

[ul]
[li]Code from scratch. Try not to use pre-exisitng functions or plugins.[/li][li]Show your script references, if any (eg. Looking at another script to assist you).[/li][li]You are encouraged to ask for help where needed, but try to do it on your own. The maxscript reference is an invaluable resource.[/li][li]Post your final script inside [/li]“`
tags (located on your posting toolbar).
[li]Post all code into THIS thread.[/li][li]Post the max version you coded in, plus any maxscript extensions you used. (Thanks galagast!)[/li][li]Try to finish the challenge in a week. There is no definite time limit.[/li][li]Voting will occur at the end of each ‘month’ (every 4 challenges).[/li][/ul]NOTES: None this week… I feel it’s pretty straightforward. If you don’t know HTML, it’s not too hard to learn, there’s numerous sites on how to write simple code for this. Remember to vote for the first 4 challenges: http://www.cgtalk.com/showthread.php?t=257580

6 Replies

this one appeared to be interesting to me, so here’s my take on it:


 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: " 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 rbLights "Lights:" labels:#("Use Scene Lights", "Use Internal Light Rig") default:2 align:#left columns:1
 	)
 	
 	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 =
 	(
 
 		-- 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
 		in coordsys cam
 			rotate cam (eulerAngles 90 0 0)
 
 		-- 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 =
 	(
 		-- create the temp camera object.
 		local camObj = genContactSheetCam obj fov renderSize
 		
 		-- 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
 	)
 	
 	
 	-- 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 = 
 	(
 		-- 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
 		) -- 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 fov = spCameraFOV.value
 		for o in objects do (
 			try (
 		    	if (contactSheetObj o outputDir HTMLFileHandle renderSize fov useSceneLights:useSceneLights imgType:imgType mode:#render) then
 					counter += 1
 			) catch(print "error")
 			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 (
 		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
 

tested only on max 7

cheers,
o

Wow, great work ofer_z! I like the handling of render styles and the easy access to the html sheet afterwards. Very well done!

Hey ofer I thought you wanted to leave the 3d arena, I liked it!!
very well done.

1 Reply
(@ofer_z)
Joined: 11 months ago

Posts: 0

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

so ofer does it mean you are ready to work (join us)?
we might use another scripter its getting busy

man it is sweet

I would add an option to break it into groups by hierarchy or skin etc` so that it would be more production worthy but it is so cool non the less