--------------------------------------------------------- -- Script for updating multiSub materials using a preset -- Stewart Wright - Rockstar North - August 2012 --------------------------------------------------------- filein (RsConfigGetWildWestDir() + "script/3dsMax/_config_files/Wildwest_header.ms") ( --------------------------------------------------------- -- LOCALS -------------------------------------------- local warnValue = 5 local updateCount = #(0,0,0,0) --(materials, rage materials, updated materials, updated to detail materials) --------------------------------------------------------- -- GLOBALS ------------------------------------------- global rs_MulitSubUpdater --------------------------------------------------------- -- FUNCTIONS -------------------------------------- --------------------------------------------------------- fn get_presetList = ( returnArray = #() presetFiles = getFiles (mapShaderPresetPath + "*.txt") for i in presetFiles do ( tempArray = filterString i @"\ /" justName = tempArray[tempArray.count] justName = substring justName 1 (justName.count-4) append returnArray justName ) return returnArray )--end get_presetList --------------------------------------------------------- --function for writing out the shader values to a text file fn saveMaterialSettings toWrite saveAs = -- toWrite = the array data we're going to write out -- saveAs = what we're saving the file as ( if (saveAs!= "") do ( safeToSave = true presetFile = (mapShaderPresetPath + saveAs + ".txt") presetFile = rsMakeSafeSlashes presetFile if (gRsPerforce.sync #(presetFile) silent:true) == true then --try and sync the file ( if not(queryBox "Preset already exists, Do you want to continue?" title:"Warning!") then (safeToSave = false) --if it already exists else ( gRsPerforce.edit #(presetFile) silent:true safeToSave = true ) ) else --if it is new ( gRsPerforce.add #(presetFile) silent:true safeToSave = true ) if (safeToSave) do ( saveFile = createFile presetFile --now we make the text file using the above settings print toWrite to: saveFile --write the contents of our array to the text file close saveFile --housekeeping. this closes the txt file rs_MulitSubUpdater.ddlPresets.items = (get_presetList()) ) ) )--end saveMaterialSettings --------------------------------------------------------- --function for reading in the shader values from a text file fn loadMaterialPreset = ( readData = #() --housekeeping. empty the array before we start presetName = rs_MulitSubUpdater.ddlPresets.selected sourceFile = mapShaderPresetPath + presetName + ".txt" shaderFile = openFile sourceFile if shaderFile != undefined then ( while not eof shaderFile do --as long as we haven't reached the end of the file keep going ( checkLine = readValue shaderFile ignoreStringEscapes:true --readValue gets the info from the txt file append readData checkLine --adds each line of the text file to our array ) close shaderFile --housekeeping. this closes the txt file readData --pass out the array ) else ( mText = "I couldn't find " + sourcefile + ".txt" messagebox mText title:"Error" ) )--end loadMaterialPreset --------------------------------------------------------- --function to read the material and save it as a preset fn readMaterial getMat = --getMat = the material we're updating ( multiSubArray = #() if classOf getMat == Multimaterial then ( for subID=1 to getMat.numSubs do ( if classOf getMat[subID] == Rage_Shader do ( shadertype = RstGetShaderName getMat[subID] shaderArray = #() --lets make a 'disposable' array for storing the settings for shaderVar=1 to RstGetVariableCount getMat[subID] do ( varValue = (RstGetVariable getMat[subID] shaderVar) --get the variable value append shaderArray varValue ) append multiSubArray #(shaderType, shaderArray) ) ) saveMaterialSettings multiSubArray rs_MulitSubUpdater.edtName.text ) else ( messageBox "This isn't a multi/sub material." ) )--end readMaterial --------------------------------------------------------- --a function to find all items in an array. similar to findItem but returns multiple values, not just the first hit fn findAllItems arrayToSearch val = ( local foundIndexes = #() local arrayCopy = deepCopy arrayToSearch while ((foundIndex = (findItem arrayCopy val)) != 0) do ( append foundIndexes foundIndex arrayCopy[foundIndex] = undefined ) foundIndexes --pass out the results )--end findAllItems --------------------------------------------------------- --a function to apply the preset values to the active material fn applyMaterial getMat presetArray= --getMat = the material we're updating --presetArray = the pesets we're loading ( if classOf getMat == Multimaterial then ( justTheShaderType = #() justTheShaderValues = #() for readShaderType=1 to presetArray.count do ( append justTheShaderType presetArray[readShaderType][1]--make an array containing just the shader type append justTheShaderValues presetArray[readShaderType][2]--make an array containing just the shader values ) for subID=1 to getMat.numSubs do--for every sub material ( updateCount[1] = (updateCount[1] + 1)--update our counter for the materials shaderUpdateStatus = false if classOf getMat[subID] == Rage_Shader do--if the shader is a rage material ( updateCount[2] = (updateCount[2] + 1)--update our counter for the rage materials shaderType = RstGetShaderName getMat[subID] --the selected shader type filteredShaderType = filterString shaderType "."--attempt to create a detail version of this shader detailShaderType = filteredShaderType[1] + "_detail." + filteredShaderType[2] matchedShaders = findAllItems justTheShaderType shaderType --search our array for shader mathes matchedDetailShaders = findAllItems justTheShaderType detailShaderType --search our array for shader mathes join matchedShaders matchedDetailShaders if matchedShaders.count > 0 do --if we've found shader matches ( matchedShaderValues = #() matchedShadersType = #() for matched=1 to matchedShaders.count do ( append matchedShadersType justTheShaderType[matchedShaders[matched]]--make a new array with just the matching shader data append matchedShaderValues justTheShaderValues[matchedShaders[matched]]--make a new array with just the matching shader data ) arrayToMatch = #() --lets make a 'disposable' array for storing the settings for shaderVar=1 to RstGetVariableCount getMat[subID] do --for each variable in the shader ( varValue = (RstGetVariable getMat[subID] shaderVar) --get the variable value varName = (RstGetVariableName getMat[subID] shaderVar) --get the variable value if (RstGetVariableType getMat[subID] shaderVar == "texmap") and (varName != "Detail Map") do --if it is a texture we'll note it, this is what we are checking for matches against ( append arrayToMatch #(varValue, shaderVar)--we are storing the value and the position within the shader ) ) for checkMe=1 to matchedShaderValues.count do --for every shader match ( if shaderUpdateStatus != true do --if we haven't found a match yet ( matchingPair = #() for checkAgainst=1 to arrayToMatch.count do --for every texture in the current shader ( source = upperCase (arrayToMatch[checkAgainst][1] as string) --the existing texture target = upperCase (matchedShaderValues[checkMe][arrayToMatch[checkAgainst][2]] as string) --the new texture to check append matchingPair (source == target) --store if we find a match. match == true ) if (findItem matchingPair false) == 0 and (matchingPair.count != 0) then --if we don't find any false matches then we can update this shader ( for shaderVar=1 to RstGetVariableCount getMat[subID] do --for every variable in the shader ( RstSetVariable getMat[subID] shaderVar matchedShaderValues[checkMe][shaderVar] --update it with our preset values if shaderVar==RstGetVariableCount getMat[subID] do ( updateCount[3] = (updateCount[3] + 1)--update our counter for the updated rage materials shaderUpdateStatus = true --set the status of the update so we can stop and move on to the next ) ) ) else-- we'll do some checks to see if this shader matches a detail version ( if shaderUpdateStatus != true do --if we haven't found a match yet ( matchedDetailShaderValues = #() for matched=1 to matchedShaders.count do ( append matchedDetailShaderValues justTheShaderValues[matchedShaders[matched]]--make a new array with just the matching shader data ) tempMatCopy = mlib[24]--copy slot24, we'll replace it once we're done mlib[24] = copy getMat[subID] RstSetShaderName mlib[24] detailShaderType if RstGetVariableCount mlib[24] > 0 do --if it is a valid shader it will have a positive number ( detailArrayToMatch = #() --lets make a 'disposable' array for storing the settings for shaderVar=1 to RstGetVariableCount mlib[24] do --for each variable in the shader ( varValue = (RstGetVariable mlib[24] shaderVar) --get the variable value varName = (RstGetVariableName mlib[24] shaderVar) --get the variable value if RstGetVariableType mlib[24] shaderVar == "texmap" do --if it is a texture we'll note it, this is what we are checking for matches against ( if upperCase varName != "DETAIL MAP" do --we don't care about the detail map. if we are going from a non detail shader to a detail shader the original shader won't have this slot ( append detailArrayToMatch #(varValue, shaderVar)--we are storing the value and the position within the shader ) ) ) mlib[24] = tempMatCopy--return slot24 to what i used to be if detailArrayToMatch.count != 0 do --if we find some matches ( for checkMe=1 to matchedDetailShaderValues.count do --for every shader match ( if shaderUpdateStatus != true do --if we haven't found a match yet ( matchingPair = #() for checkAgainst=1 to arrayToMatch.count do --for every texture in the current shader ( source = upperCase (detailArrayToMatch[checkAgainst][1] as string) --the existing texture target = upperCase (matchedDetailShaderValues[checkMe][detailArrayToMatch[checkAgainst][2]] as string) --the new texture to check append matchingPair (source == target) --store if we find a match. match == true ) if (findItem matchingPair false) == 0 and (matchingPair.count != 0) then --if we don't find any false matches then we can update this shader ( RstSetShaderName getMat[subID] matchedShadersType[checkMe] --change the shader type for shaderVar=1 to RstGetVariableCount getMat[subID] do --for every variable in the shader ( RstSetVariable getMat[subID] shaderVar matchedDetailShaderValues[checkMe][shaderVar] --update it with our preset values if shaderVar==RstGetVariableCount getMat[subID] do ( updateCount[4] = (updateCount[4] + 1)--update our counter for the updated rage materials shaderUpdateStatus = true --set the status of the update so we can stop and move on to the next ) ) ) ) ) ) ) mlib[24] = tempMatCopy--return slot24 to what i used to be# ) )--end of else loop. this loop is for detail shader upgrades ) ) )--end shader match ) )-- end for every sub material loop ) )--end applyMaterial --------------------------------------------------------- -- UI -------------------------------------------------- --------------------------------------------------------- ( try (destroyDialog rs_MulitSubUpdater) catch() -- LOCALS ------------------------------------------- local theState = 1 --------------------------------------------------------- rollout rs_MulitSubUpdater "MultiSub Material Presets" ( dotNetControl rsBannerPanel "Panel" pos:[0,0] height:32 width:rs_MulitSubUpdater.width local banner = makeRsBanner dn_Panel:rsBannerPanel wiki:"" filename:(getThisScriptFilename()) group "Create New MultiSub Preset" ( label presetName "Preset Name:" align:#left editText edtName width:140 offset:[0,0] button btnSave "Save MultiSub As Preset" width:130 tooltip:"Create preset from selecte material" ) group "MultiSub Shader Preset" ( button btnFetch "Sync Presets" width:130 toolTip:"Fetch the latest data from perforce" dropdownlist ddlPresets width:140 items:(get_presetList()) label materialSource "Update Material On Selected:" align:#left radiobuttons radObjectOrMaterial labels:#("Object", "Material") toolTip:"Select how you want to define the material to update" button btnLoad "Apply MultiSub Preset" width:130 toolTip:"Load preset on to selected material." ) --------------------------------------------------------- -- EVENTS ------------------------------------------ --------------------------------------------------------- on ddlPresets selected sel do (loadMaterialPreset()) ----------------------------------------------------------- on radObjectOrMaterial changed newState do (theState = newState) ----------------------------------------------------------- on btnSave pressed do ( materialWindowState = MatEditor.isOpen()--store the material editor window state MatEditor.Close()--close the material editor if it is open readMaterial (medit.getcurmtl()) if materialWindowState == true do MatEditor.Open()--open the material editor again if it was when we started ) on btnLoad pressed do ( if rs_MulitSubUpdater.ddlPresets.selected != undefined then ( loadedData = loadMaterialPreset() updateCount = #(0,0,0,0)--empty this, we'll fill it with report stats case of ( (theState == 1):--if the 'object' button is checked ( if selection.count > 0 then--if we have at least 1 thing selected ( continueState = true whatMaterials = #()--an array to store the materials in for selectedObject=1 to selection.count do ( if superClassOf selection[selectedObject] == GeometryClass then ( if classOf selection[selectedObject].material == Multimaterial then ( whatMaterial = selection[selectedObject].material appendIfUnique whatMaterials whatMaterial --add the material if it is unique ) else (print "The material is not a multiSub.") ) else (print "The selected object is not geometry.") ) if selection.count > warnValue do--a warning about large selection counts ( continueState = (queryBox "There are more than 5 objects selected.\r\nThis might take a while.\r\nDo you want to continue?" title:"Warning!") ) if (whatMaterials.count > 0) and (continueState == true) do --if we have some materials to update ( materialWindowState = MatEditor.isOpen()--store the material editor window state MatEditor.Close()--close the material editor if it is open for singleMaterial=1 to whatMaterials.count do --for every material ( applyMaterial whatMaterials[singleMaterial] loadedData --update the material ) if materialWindowState == true do MatEditor.Open()--open the material editor again if it was when we started updateStatsText = updateCount[1] as string + " total materials checked. Of which " + updateCount[2] as string + " were Rage Shaders.\r\n" + updateCount[3] as string + " Rage Shaders updated.\r\n" + updateCount[4] as string + " Rage Shaders converted to details shaders.\r\n" messageBox updateStatsText title:"Update Stats Report:" ) ) else (messageBox "Please select a mesh.") ) (theState == 2):--if the 'material' button is checked ( if classOf (medit.getcurmtl()) == Multimaterial then--if the material is a rage shader we'll use it ( materialWindowState = MatEditor.isOpen()--store the material editor window state MatEditor.Close()--close the material editor if it is open whatMaterial = medit.getcurmtl() applyMaterial whatMaterial loadedData if materialWindowState == true do MatEditor.Open()--open the material editor again if it was when we started updateStatsText = updateCount[1] as string + " total materials checked. Of which " + updateCount[2] as string + " were Rage Shaders.\r\n" + updateCount[3] as string + " Rage Shaders updated.\r\n" + updateCount[4] as string + " Rage Shaders converted to details shaders.\r\n" messageBox updateStatsText title:"Update Stats Report:" ) else (messageBox "Please open the material editor and select a multiSub shader.") ) ) ) else (messageBox "Please select a preset.") ) ----------------------------------------------------------- on btnFetch pressed do ( WWP4vSync mapShaderPresetFiles rs_MulitSubUpdater.ddlPresets.items = (get_presetList()) ) --------------------------------------------------------- --------------------------------------------------------- on rs_MulitSubUpdater open do ( rs_dialogPosition "get" rs_MulitSubUpdater rs_MulitSubUpdater.banner.setup() ) --------------------------------------------------------- on rs_MulitSubUpdater close do ( rs_dialogPosition "set" rs_MulitSubUpdater ) ) createDialog rs_MulitSubUpdater width:165 style:#(#style_border,#style_toolwindow,#style_sysmenu) )--end UI )