--************************************************************************************* --* 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 += "\t" 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 "%Texture Files:
\n" indent to:HTMLFileHandle format "%\n" indent to:HTMLFileHandle decIndent &indent ) ) fn writeObjHTMLCode HTMLFileHandle obj photoName &indent = ( try ( format "%\n" indent to:HTMLFileHandle incIndent &indent format "%\n" indent to:HTMLFileHandle incIndent &indent format "%\"%\"\n" indent photoName obj.name to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle format "%\n" indent to:HTMLFileHandle incIndent &indent format "%Object Name:%
\n" indent obj.name to:HTMLFileHandle format "%Vertices:%
\n" indent (getNumVerts obj) to:HTMLFileHandle format "%Faces:%
\n" indent (getNumFaces obj) to:HTMLFileHandle writeObjTexturesHTML HTMLFileHandle obj photoName &indent decIndent &indent format "%\n" indent to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle -- create a divider format "%\n" indent to:HTMLFileHandle incIndent &indent format "%\n" indent to:HTMLFileHandle incIndent &indent format "%
\n" indent photoName obj.name to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle )catch() ) fn writeHTMLHeaderCode HTMLFileHandle &indent = ( try ( format "%\n" indent to:HTMLFileHandle format "%\n" indent to:HTMLFileHandle format "%\n" indent to:HTMLFileHandle incIndent &indent format "%contactSheet - 3DS Max Object Contact Sheet\n" indent to:HTMLFileHandle -- write the styles format "%\n" indent to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle format "%\n" indent to:HTMLFileHandle incIndent &indent format "%\n" indent to:HTMLFileHandle incIndent &indent )catch() ) fn writeHTMLFooterCode HTMLFileHandle &indent = ( try ( decIndent &indent format "%
\n" indent to:HTMLFileHandle decIndent &indent format "%\n" indent to:HTMLFileHandle decIndent &indent format "%\n" 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 "%\n" 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.\nPlease 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?\nThis 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