--------------------------------- Multi Texture Tiler Plugin --------------------------- --------------------------------- (C) Mirko Haenssgen, 2012 ----------------------------- --------------------------------- http://www.kogenspage.com ---------------------------- -------------------------------------------------------------------------------------------- -- Version 1.04 --------------------------------------------------------------------------- -- 27.09.2012 ----------------------------------------------------------------------------- -- Description /* This Plugin allows it to use a bunch of textures based upon their UV-tiles and their proper numbers. Every square represents a square in UV-Space. Normally you'd work in 0..1 area but if you're dealing with multiple areas you'd have to set up a Composite Material and link each texture to a new slot. "Multi Texture Tiler" eases this tedious process by putting all textures of a folder at their distinct UV-tile-places. When you're working with folders (by folder): Just make sure every texture has a proper ending made up four numbers like "myfile0X0X.type" where X is a number from 0 to 9. The file "barbiehand0104.jpg" would offset the texture at the UV position: U: 1 and V: 4. Note: Texture Tiling is diabled in "Multi Texture Tiler". When Rendering the your model you will see the new texture (if you offset the UV-islands of the distinct parts by UV[1,4] before). When you are working with files (by files): It does not matter which ending your texture has. Click on the checkbutton "select files" and then click on cell, choose your image and you're done. */ -- Future things to improve: -- Click a texture and let it be the visible texture-element in UV-editor background -- Automatic refresh (?) -- And now the code: -- This Class contains functions used for the future texture array, it also contains the array itself Struct StrArray ( sizeArr = 10, -- the size of the array (10 x 10 as default) ArrTex = #(), -- the array containing textures function createTextureArray = -- sets up the Texture array according to the textures ( ArrTex = #() for lvy = 1 to sizeArr do ( append ArrTex #() for lvx = 1 to sizeArr do ( append ArrTex[lvy] "" ) ) ), function isValidTextureFile str = -- returns true if the passed filename ending is specified as texture filename ( case (toLower (getFileNameType str)) of ( ".avi" : return true ".mpg" : return true ".mpeg" : return true ".bmp" : return true ".cin" : return true ".cws" : return true ".exr" : return true ".fxr" : return true ".gif" : return true ".hdr" : return true ".pic" : return true ".ifl" : return true ".jpg" : return true ".jpe" : return true ".jpeg" : return true ".png" : return true ".psd" : return true ".rgb" : return true ".rgba" : return true ".sgi" : return true ".int" : return true ".inta" : return true ".bw" : return true ".rla" : return true ".rpf" : return true ".tga" : return true ".vda" : return true ".icb" : return true ".vst" : return true ".tif" : return true ".yuv" : return true ".vrimg" : return true ".dds" : return true ) return false ), function fillTextureArray root = -- fills the array with all Textures beeing found in the provided folder ( ArrFiles = getFiles (root + "/*") for obj in ArrFiles do--ArrFiles.count do ( str = (getFileNameFile obj) try ( -- pntX = (str[str.count-3]+str[str.count-2]) as integer -- pntY = (str[str.count-1]+str[str.count]) as integer -- pntY = (sizeArr - pntY) UDIM = (substring str (str.count-3) 4) as integer UVID = UDIM - 1001 pntX = (mod UVID sizeArr)+1 pntY = UVID/sizeArr +1 pntY = (sizeArr - pntY) if (isValidTextureFile obj) then -- when the provided file is a texture ( -- ArrTex[pntY][pntX+1] = obj ArrTex[pntY+1][pntX] = obj ) ) catch ( -- just in case pntX or pntY could not been read, do nothing, so the slot won't be filled with the texture name ) ) ), function fillTextureArrayWithArray ArrFiles = ( for lvi = 0 to ArrFiles.count-1 do ( lvy = (lvi / sizeArr) +1 lvx = (mod lvi sizeArr)+1 if ArrFiles[lvi+1] != undefined then ( ArrTex[lvy][lvx] = ArrFiles[lvi+1] ) else ( ArrTex[lvy][lvx] = "" ) ) ), function getStringPosition cellX cellY cells = ( str = "" if (cellX) < cells then ( str+= "0"+((cellX) as string) ) else ( str+= ((cellX) as string) ) -- prepare the Y-numbers for the squares cellY = (cells - cellY - 1) as integer if cellY < cells then ( str+= "0"+((cellY) as string) ) else ( str+= ((cellY) as string) ) str ), function getUsedTextures = -- returns a point2-array containing the cell in U and V e.g.: [4,5] -> that's NOT the position in Px it's the array position ( ArrPoints = #() try ( for lvy = 1 to ArrTex.count do ( for lvx = 1 to ArrTex[lvy].count where (ArrTex[lvx][lvy] != "" and ArrTex[lvx][lvy] != undefined) do ( -- include a test, if the specified file is a bitmap if (doesFileExist ArrTex[lvx][lvy]) then append ArrPoints (Point2 lvx lvy) ) ) ) catch() ArrPoints ) ) -- this class intializes a gridbox which is constructed from a picturebox. So we do some painting here using .Net :) Struct StGridBox ( fn init pb size loc cells = -- wants picturebox-control, the size, the position and the number of cells ( pb.width = size.x pb.height = size.y bm = (dotNetObject "System.Drawing.Bitmap" size.x size.y) -- create a bitmap that matches the size of the picturebox graph =(dotNetClass "System.Drawing.Graphics").FromImage bm -- a graph object to do some painting pen = (dotNetObject "System.Drawing.Pen" (dotNetClass "System.Drawing.Color").Black) -- crayon -- Background brush = dotNetObject "System.Drawing.Solidbrush" (dotNetClass "System.Drawing.Color").darkgray rect = dotNetObject "System.Drawing.rectangle" 0 0 size.x size.y graph.FillRectangle brush rect -- Init and Draw the Grid Lines for lvi = 0 to cells do -- vertical lines ( graph.DrawLine pen (lvi*(size.x/cells)) 0 (lvi*(size.x/cells)) size.y ) for lvi = 0 to cells do -- horizontal lines ( graph.DrawLine pen 0 (lvi*(size.y/cells)) size.x (lvi*(size.y/cells)) ) -- Add numbers to the squares brush = dotNetObject "System.Drawing.Solidbrush" (dotNetClass "System.Drawing.Color").gray font = dotNetObject "System.Drawing.Font" "Arial" (size.y/(cells*4)) for lvy = 0 to size.y by (size.y/cells) do ( for lvx = 0 to size.x by (size.x/cells) do ( str = "" -- prepare the X-numbers for the squares valX = ((lvx*cells/size.x) as integer) if valX < 10 then ( str+= "0"+(valX as string) ) else ( str+= (valX as string) ) -- prepare the Y-numbers for the squares valY = ((lvy*cells/size.y) as integer) valY = cells - valY - 1 if valX < 10 then ( str+= "0"+(valY as string) ) else ( str+= (valY as string) ) graph.DrawString str font brush (lvx+(size.x*0.1/cells)) (lvy+(size.y*0.25/cells)) ) ) pb.Image = bm -- assign the painted bitmap as Image to the picture box ) ) plugin textureMap MultiTextureTiler name:"Multi Texture Tiler" classID:#(0x7c31a24, 0x347c5c8a) extends:CompositeTextureMap replaceUI:true ( local panelheight = 300 -- the height and width for the pb local cells = 10 -- number of cells local strarr = StrArray sizeArr: cells -- Multi-Texturer Parameters parameters paramultitex rollout:rmultitex -- rollout is only used for ui-flags ( pPath type:#string animatable:false default:"" pStrArr type: #StringTab animatable: false tabSize:100 tabSizeVariable:true pOpt type: #integer animatable: false default: 1 ui: radbtnfolder ) -- Display Parameters parameters paradisplay rollout: rdisplay ( pUseThumb type: #boolean animatable: false default: true ui:ckbimage ) function addMaps ArrTexIndices = -- uses the underlying features of the Composite Texture to create layers where the images are stored ( if delegate.mapList.count > 1 then -- if there are slots, delete them to make sure we always use the correct amount of slots ( for lvi = delegate.MapList.count to 2 by -1 do -- delete existing slots ( delegate.delete lvi ) ) for lvi = 2 to ArrTexIndices.count do -- create new slots ( delegate.add() ) -- assign a new bitmap linked with the image-files to each Composite texture-layer (this is done by using the point2 from getUsedTextures for lvi = 1 to delegate.mapList.count where ArrTexIndices[lvi] != undefined do ( obj = ArrTexIndices[lvi] bm = Bitmaptexture filename: strarr.ArrTex[obj.X][obj.Y] --create a bitmap with the path from the array bm.coords.U_Offset = obj.Y - 1 bm.coords.V_Offset = (strarr.sizeArr - obj.X) bm.coords.U_Tile = false --don't tile bm.coords.V_Tile = false delegate.mapList[lvi] = bm -- assign the bitmap ) ) -- sets the new button.caption when a new folder has been chosen function setTexFolder str btnfolder flagfolder = ( if flagfolder == 1 then -- if we use a texture folder ( if (str == undefined or str == "") then -- if the user aborted ( if (pPath == undefined or pPath == "") then -- if the local variable for storing the full path== undefined ( btnfolder.caption = "Select Texture Folder..." -- display standard text strarr.createTextureArray() return false ) else ( str = pPath -- if the user aborted while having selected a folder earlier so the full path has been stored, keep the old path ) ) else -- we've got a new path ( strarr.createTextureArray() -- recreate the array strarr.fillTextureArray str -- fill it with the textures from that folder addMaps (strarr.getUsedTextures()) -- and get the proper positions + add layers pPath = str -- if the user has chosen a new path, store that chosen path on the local variable ) -- note: this block affects only the appearence of the path on the button, it does not affect the string in the local variable if str != undefined then -- if there has been chosen a path earlier ( if str.count > 35 then -- if the chosen path is longer than 35 chars ( btnfolder.caption = ((substring str 1 30) + "...") -- shorten it ) else ( btnfolder.caption = str -- display it otherwise ) ) ) else ( strarr.createTextureArray() -- recreate the array strarr.fillTextureArrayWithArray pStrArr addMaps (strarr.getUsedTextures()) -- and get the proper positions + add layers ) ) rollout rmultitex "Multi Texture Tiler" ( local strpb = StGridBox() local currentimage = undefined label lblname width: (panelheight-5) height: 18 pos: [15, 10] dotNetControl pb "System.Windows.Forms.PictureBox" button btnfolder "Select Texture Folder..." width: (panelheight/1.5) height: 18 pos:[pb.pos.x, (panelheight+50)] enabled: (if pOpt == 2 then false else true) checkbutton chkbtnfiles "Select Files" width: (panelheight/3.5) height: 18 pos:[pb.pos.x+215, (panelheight+50)] enabled: (if pOpt == 1 then false else true) radiobuttons radbtnfolder labels:#("By Folder\t\t\t\t\t\t", "By File") default:1 columns:2 pos:[pb.pos.x+110, (panelheight+75)] label lblfill width: (panelheight-5) height: 18 pos:[pb.pos.x, (btnfolder.pos.y+40)] on rmultitex open do ( strpb.init pb [panelheight, panelheight] [10, 10] cells -- init the picturebox if pPath != "" then -- and setup the textures in their matrix ( setTexFolder pPath btnfolder radbtnfolder.state ) if radbtnfolder.state == 2 then ( setTexFolder pPath btnfolder radbtnfolder.state ) ) on pb MouseDown arg do -- when the user clicks on a cell in the picture box ( -- Left click to open if arg.Button == (dotNetClass "System.Windows.Forms.MouseButtons").Left and chkbtnfiles.checked == false then ( cellX = arg.X / (pb.width/cells) +1 cellY = arg.Y / (pb.height/cells) +1 --abs((arg.Y / (pb.height/cells)) - cells) -- basically <- this is the right code but the array is sorted backwards try ( lblname.caption = (filenameFromPath strarr.ArrTex[cellY][cellX]) ) catch ( lblname.caption = "" ) if currentimage != undefined then close currentimage try ( if (doesFileExist strarr.ArrTex[cellY][cellX]) then ( currentimage = openBitMap strarr.ArrTex[cellY][cellX] -- display an image of the texture in the vfb display currentimage ) ) catch ( currentimage = undefined ) ) -- Left click with checkbutton on if arg.Button == (dotNetClass "System.Windows.Forms.MouseButtons").Left and chkbtnfiles.checked == true then -- select Files is activated ( cellX = arg.X / (pb.width/cells) cellY = arg.Y / (pb.height/cells) --abs((arg.Y / (pb.height/cells)) - cells) -- basically <- this is the right code but the array is sorted backwards str = strarr.getStringPosition cellX cellY cells cellY = (cells - cellY - 1) as integer bm = selectBitmap caption: ("Select a file for cell " + str) if bm != undefined then ( strarr.ArrTex[(cellY+1)][(cellX+1)] = bm.filename ) else ( strarr.ArrTex[(cellY+1)][(cellX+1)] = "" ) pStrArr[((abs (strarr.sizeArr - cellY - 1))*strArr.sizeArr + (cellX + 1))] = strarr.ArrTex[(cellY+1)][(cellX+1)] strarr.fillTextureArrayWithArray pStrArr addMaps (strarr.getUsedTextures()) -- and get the proper positions + add layers pb.Refresh() ) -- Right click to close if arg.Button == (dotNetClass "System.Windows.Forms.MouseButtons").Right and chkbtnfiles.checked == false then ( if currentimage != undefined then close currentimage ) -- Right click with check button on if arg.Button == (dotNetClass "System.Windows.Forms.MouseButtons").Right and chkbtnfiles.checked == true then -- select Files is activated ( cellX = arg.X / (pb.width/cells) cellY = arg.Y / (pb.height/cells) --abs((arg.Y / (pb.height/cells)) - cells) -- basically <- this is the right code but the array is sorted backwards str = strarr.getStringPosition cellX cellY cells cellY = (cells - cellY - 1) as integer strarr.ArrTex[(cellY+1)][(cellX+1)] = "" pStrArr[((abs (strarr.sizeArr - cellY - 1))*strArr.sizeArr + (cellX + 1))] = strarr.ArrTex[(cellY+1)][(cellX+1)] strarr.fillTextureArrayWithArray pStrArr addMaps (strarr.getUsedTextures()) -- and get the proper positions + add layers pb.Refresh() ) ) -- When the picture box gets painted on pb Paint args do ( graph = args.Graphics brush = dotNetObject "System.Drawing.Solidbrush" (dotNetClass "System.Drawing.Color").Green for obj in (strarr.getUsedTextures()) do -- get an array of the proper cells ( -- recalculate the position from the cell numbers posX = ((obj.X - 1) * (pb.width/cells) +1) posY = ((obj.Y - 1) * (pb.width/cells) +1) rect = dotNetObject "System.Drawing.rectangle" posY posX ((pb.width/cells)-1) ((pb.width/cells)-1) -- sorting in the array starts with y then x that's why we've flipped coords -- This block is used for the naming of the squares when not using their thumbnails font = dotNetObject "System.Drawing.Font" "Arial" (pb.height*0.25/cells) str = "" -- note that the order is confused in this block X =Y and Y = X valY = (obj.y - 1) as integer if (valY) < cells then ( str+= "0"+(valY as string) ) else ( str+= (valY as string) ) -- prepare the Y-numbers for the squares valX = (cells - obj.x) as integer if valX < cells then ( str+= "0"+(valX as string) ) else ( str+= (valX as string) ) -- if the parameter pUseThumb is set true if pUseThumb == true then ( try ( img = (dotNetObject "System.Drawing.bitmap" strarr.ArrTex[obj.X][obj.Y]) graph.DrawImage img rect ) catch ( -- In case the image could not be displayed because the format is not supported brush.color = (dotNetClass "System.Drawing.Color").darkred graph.FillRectangle brush rect -- Add numbers to the squares brush.color = (dotNetClass "System.Drawing.Color").red graph.DrawString str font brush (posY+(pb.height*0.05/(cells)) ) (posX+(pb.height*0.22/(cells)) ) ) ) else -- when we are not in "Display image"-mode ( brush.color = (dotNetClass "System.Drawing.Color").green graph.FillRectangle brush rect -- Add numbers to the squares brush.color = (dotNetClass "System.Drawing.Color").darkgreen graph.DrawString str font brush (posY+(pb.height*0.05/(cells)) ) (posX+(pb.height*0.22/(cells)) ) ) ) ) on radbtnfolder changed state do ( setTexFolder pPath btnfolder pOpt case pOpt of ( 1: ( chkbtnfiles.enabled = false chkbtnfiles.checked = false btnfolder.enabled = true ) 2: ( btnfolder.enabled = false chkbtnfiles.enabled = true chkbtnfiles.state = false ) ) pb.Refresh() ) on btnfolder pressed do -- user pressed the "Select Folder..."-Button ( str = getSavePath caption: "Select a texture folder..." initialDir: pPath setTexFolder str btnfolder pOpt pb.Refresh() ) ) rollout rdisplay "Display Options" -- this rollout provides some functionalities for displaying the elements ( groupbox grp1 "Display Functionality" width: (panelheight) height: 50 checkbox ckbimage "Use Preview-Images" pos: [grp1.pos.x+10, grp1.pos.y+20] button btnrefresh "Refresh grid" pos: [grp1.pos.x+210, grp1.pos.y+18] on rdisplay open do ( rdisplay.open = false ) on btnrefresh pressed do -- refreshes the displayed elements (in case the user added a new texture) ( setTexFolder pPath rmultitex.btnfolder pOpt rmultitex.pb.Refresh() ) on ckbimage changed args do -- repaints the picture box so it has to use the "Paint"-method and decides wether to draw the image or a green square ( rmultitex.pb.Refresh() ) ) )