-- Scene shader-optimiser: -- Looks for materials in scene that can be auto-optimised, and offers to apply optimisations. -- Neal D Corbett 13/07/2012 try (destroyDialog RsShaderOptimiserRoll) catch () rollout RsShaderOptimiserRoll "Shader Optimiser [v1.19:DidacticWire]" width:320 height:100 ( -- Phrase generated via: (filein (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms"); print (RsRandomPhrase count:100)) local shadersPath = RsConfigGetCommonDir() + "shaders/db/*.sps" local shaderNames = for filename in (getFiles shadersPath) collect (toLower (getFilenameFile filename)) local bannerHeight = 32 dotNetControl RsBannerPanel "Panel" pos:[0,0] height:bannerHeight width:RsShaderOptimiserRoll.width local banner = makeRsBanner dn_Panel:RsBannerPanel wiki:"ShaderOptimiser" filename:(getThisScriptFilename()) button btnCheckForNonoptimal "Find Non-Optimal Shaders" pos:(RsBannerPanel.pos + [5, bannerHeight + 5]) width:(RsShaderOptimiserRoll.width - 10) height:50 button btnOpenCSV "Edit Alternative-Shaders List" tooltip:"Opens CSV-file containing alternative shadernames used by tool" pos:(btnCheckForNonoptimal.pos + [0, 55]) width:(RsShaderOptimiserRoll.width - 10) height:30 -- This CSV-file contains a list of alternative shader-names: local listFilename = RsConfigGetProjRootDir() + "/assets/maps/shader_optimiser.csv" -- Suggested changes are added to this array: local suggestionList = #() local shaderLookupList, shaderLookupListNames fn loadShaderLookupList = ( local readFile = openFile listFilename if (readFile == undefined) do return false struct shaderLookupItem ( shaderName, -- Data loaded from csv: noBump, noSpec, noAlpha, noReflect, noBumpSpec, noBumpAlpha, noBumpReflect, noSpecAlpha, noSpecReflect, noAlphaReflect, noBumpSpecAlpha, noBumpSpecReflect, noSpecAlphaReflect, noBumpAlphaReflect, noBumpSpecAlphaReflect, -- Used to say what features this shader supports: processed = False, isAlphaShader = False, isCutOut = False, isAlpha = False, isBump = False, isSpec = False, isReflect = False, fn initFeatures mat = ( -- Only needs to be run once per shadername: if processed do return True -- Is this is a Cutout Alpha shader? Check name: isCutout = (matchPattern shaderName pattern:"*cutout*") isAlpha = (isCutout) -- Set features to True if CSV included alternates for shaders missing particular features: for propName in (getPropNames This) where (matchPattern propName pattern:"no*") do ( if ((getProperty This propName) != undefined) do ( if (not isAlpha) and (matchPattern propName pattern:"*alpha*") do ( isAlpha = True ) if (not isSpec) and (matchPattern propName pattern:"*spec*") do ( isSpec = True ) if (not isBump) and (matchPattern propName pattern:"*bump*") do ( isBump = True ) if (not isReflect) and (matchPattern propName pattern:"*reflect*") do ( isReflect = True ) ) ) if isAlpha do ( isAlphaShader = RstGetIsAlphaShader mat ) processed = True ) ) shaderLookupList = #() local headerNames local validHeaders = #{} while not eof readFile do ( local thisLine = (toLower (trimRight (RsRemoveCharacterFromString (readLine readFile) "\""))) case of ( (thisLine == ""):() -- Ignore empty/whitespace lines (thisLine[1] == "#"):() -- Ignore hashed comments (headerNames == undefined): -- First line is header ( headerNames = for token in (filterString thisLine ",") collect ((trimLeft (trimRight token)) as name) -- Only use header-names that are in struct: local structPropNames = (getPropNames shaderLookupItem) for n = 1 to headerNames.count do ( validHeaders[n] = (findItem structPropNames headerNames[n] != 0) ) ) Default: ( -- Load the actual data-lines: local lineTokens = for token in (filterString thisLine "," splitEmptyTokens:True) collect (trimLeft (trimRight token)) if (lineTokens.count != 0) do ( local newItem = shaderLookupItem() for n = validHeaders do ( local thisToken = lineTokens[n] if (thisToken != undefined) and (thisToken != "") do ( setProperty newItem headerNames[n] thisToken ) ) append shaderLookupList newItem ) ) ) ) close readFile shaderLookupListNames = for item in shaderLookupList collect item.shaderName return true ) -- Used to find the maximum value, or range of values, used on a bitmap: fn getBmpRange filename bmpResize:64 maxValue:false getAlphaType:false rgbaChans:#{1..3} slotNum:1 = ( if getAlphaType do ( rgbaChans = #{4} ) local fileBmp = openBitmap filename local fileBmpHasAlpha = fileBmp.hasAlpha -- Abort check if doing alpha-check on non-alpha bitmap in first slot: if (getAlphaType) and (slotNum == 1) and (not fileBmpHasAlpha) do ( close fileBmp return #none ) local doResize = (isKindOf bmpResize number) local readBmp -- Scale down bitmap, to speed up processing: if doResize then ( local bmpSize = [bmpResize,bmpResize] local RGBOutputIdx = if (getAlphaType and (slotNum == 1)) then 1 else 0 readBmp = renderMap (bitmapTexture bitmap:fileBmp RGBOutput:RGBOutputIdx) size:bmpSize filter:false close fileBmp ) else ( readBmp = fileBmp ) local bmpWidth = readBmp.width local usedValsR = #{} local usedValsG = #{} local usedValsB = #{} local usedValsA = #{} local usedVals = #(usedValsR, usedValsG, usedValsB, usedValsA) for item in usedVals do (item.count = 256) local doR = rgbaChans[1] local doG = rgbaChans[2] local doB = rgbaChans[3] local doA = getAlphaType or rgbaChans[4] local alphaLineA = #{} local alphaLineB = #{} local alphaLineC = #{} local isCutout = true for rowNum = 0 to (readBmp.height - 1) do ( local readRow = getPixels readBmp [0, rowNum] bmpWidth if doR do ( for clr in readRow do (usedValsR[clr.R + 1] = true) ) if doG do ( for clr in readRow do (usedValsG[clr.G + 1] = true) ) if doB do ( for clr in readRow do (usedValsB[clr.B + 1] = true) ) if doA do ( local alphaVals = if (fileBmpHasAlpha and (not doResize)) then ( for clr in readRow collect clr.a ) else ( -- Assuming rgb is monochrome, so just need to read one colour-channel: for clr in readRow collect clr.r ) for val in alphaVals do (usedValsA[val + 1] = true) -- Set as non-cutout if bitmap contains non-monochrome pixels with no monochrome neighbours: if getAlphaType and isCutout do ( alphaLineA = alphaLineB alphaLineB = alphaLineC -- Set alphaLineC as bitarray of monochrome/non-monochrome pixels: for n = 1 to alphaVals.count do ( alphaLineC[n] = ((alphaVals[n] <= 10) or (alphaVals[n] >= 245)) ) -- Only start checking once all alphaLine arrays are filled: if (rowNum > 1) do ( -- Check non-monochrome pixels: for n = 2 to (alphaLineB.count - 1) where (not alphaLineB[n]) while isCutout do ( -- True if any neighbour is monochrome: isCutout = alphaLineA[n - 1] or alphaLineA[n] or alphaLineA[n + 1] or alphaLineB[n - 1] or alphaLineB[n + 1] or alphaLineC[n - 1] or alphaLineC[n] or alphaLineC[n + 1] ) ) ) ) ) if doResize then ( close readBmp ) else ( close fileBmp ) -- Return a point4 if showing alpha, else return a point3: local valRange = if (rgbaChans.count == 4) then [0,0,0,0] else [0,0,0] local retAlphaType -- Find value-range for requested channels: for n = rgbaChans do ( -- Get bitarray of values, as array: local chanVals = usedVals[n] as array -- Get max value: valRange[n] = chanVals[chanVals.count] - 1 -- Check for alpha/cutout: if getAlphaType and (n == 4) do ( -- Bno has suitable alpha-channel if lowest value is noticably less than white, or it has a reasonable difference between its dark/bright values: local hasAlpha = ((chanVals[1] <= 245) or ((valRange[n] - chanVals[1] - 1) > 10)) -- Has Cutout if bmp only contains very dark/bright values: local hasCutout = hasAlpha for val in chanVals while hasCutout do ( hasCutout = ((val <= 10) or (val >= 245)) ) retAlphaType = case of ( hasCutout:#cutout hasAlpha:#alpha default:#none ) ) -- Get max/min range: if not maxValue do ( valRange[n] -= (chanVals[1] - 1) ) ) -- Return alpha-type or 0->1 ranged values: if getAlphaType then (return retAlphaType) else (return (valRange / 255.0)) ) -- Function to replace instances of mat is scene with a Rage version: fn replaceStdWithRage mat unused = ( local newMat = RstCreateRstMtlFromStdMtl mat for obj in geometry do ( if isKindOf obj.material multiMaterial then ( local matList = obj.material.materialList for n = 1 to matList.count where (matList[n] == mat) do ( matList[n] = newMat ) ) else ( if obj.material == mat do ( obj.material = newMat ) ) ) ) -- Called when list is double-clicked: fn listDoubleClick Sender Args = ( local rowNum = args.RowIndex + 1 if (rowNum <= 0) do return false -- Open new material-editor (closing first, as existing one can't be focused directly) -- (also sets to non-Slate mode, as I don't think Slate can be controlled from script) MatEditor.Close() MatEditor.mode = #basic MatEditor.Open() local clickedMat = suggestionList[rowNum].mat if (clickedMat != undefined) do ( local matEditSlot = medit.GetActiveMtlSlot() medit.PutMtlToMtlEditor clickedMat matEditSlot ) Sender.Parent.Dispose() ) fn checkForNonOptimal = ( if (loadShaderLookupList() == False) do ( messageBox ("Aborting: Failed to load list:\n\n" + listFilename) title:"Error: List-file not loaded" return false ) local timeStart = timeStamp() local notCancelled = true local objs = for obj in geometry where (isEditPolyMesh obj) collect obj local objsCount = objs.count progressStart ("Collecting mats from " + (objsCount as string) + " objs:") local matList = #() local objFaceMats = for objNum = 1 to objsCount while (notCancelled = progressUpdate (100.0 * objNum / objsCount)) collect ( local obj = objs[objNum] local matFaces = (for item in matList collect #{}) RsGetMaterialsOnObjFaces obj materials:matList faceLists:matFaces dataPair obj:obj matFaces:matFaces ) progressEnd() pushPrompt "Filtering down to unique materials..." ( local uniqueMats = makeUniqueArray matList if (matList.count != uniqueMats.count) do ( -- Generate lookup-list: local newIdxList = for matNum = 1 to matList.count collect (findItem uniqueMats matList[matNum]) local nonUniques = #{} nonUniques.count = matList.count for n = 1 to newIdxList.count where (newIdxList[n] != n) do ( nonUniques[n] = True ) -- Change matFaces for each object to use new indexes: for item in objFaceMats do ( local matFaces = item.matFaces for matNum = 1 to matFaces.count where nonUniques[matNum] do ( local theseMatFaces = matFaces[matNum] if (theseMatFaces.numberSet != 0) do ( local newIdx = newIdxList[matNum] -- Move these faces to the face-list for equivalent unique material: matFaces[newIdx] += theseMatFaces theseMatFaces.count = 0 ) ) ) matList = uniqueMats ) ) popPrompt() format "Data-aquisition time: %s\n" ((timeStamp() - timeStart) / 1000.0) if (not notCancelled) do (return false) struct matSuggest (mat, action = "", reason = "", func, arg, ticked = true) suggestionList = #() -- Threshold for low-value multipliers: local varThreshold = 0.02 -- Multiplier-value local texThreshold = 0.03 -- Bitmap-channel value local varTexThreshold = 0.01 -- Combined value local matCount = matList.count progressStart ("Examining " + (matCount as string) + " materials:") local timeStart = timeStamp() listedMats = #() for matNum = 1 to matCount while (notCancelled = progressUpdate (100.0 * matNum / matCount)) do ( local mat = matList[matNum] case classOf mat of ( StandardMaterial: ( append suggestionList (matSuggest mat:mat action:"Replace with Rage material" reason:"Uses StandardMaterial" func:replaceStdWithRage) ) Rage_Shader: ( local matShaderName = toLower (getFilenameFile (RstGetShaderName mat)) local lookupNum = findItem shaderLookupListNames matShaderName -- Check spec/bump data for listed shaders: if (lookupNum != 0) do ( local shaderItem = shaderLookupList[lookupNum] -- Make sure shader's fingerprint has been taken, to see what features need to be tested for: shaderItem.initFeatures mat local missingFiles = #() local noDiffTex = true local hasDiffSlot = false local noAlphaTex = true local alphaType = #none local noSpecTex = true local noSpecVal = false local hasSpecSlot = false local noBumpTex = true local noBumpVal = false local hasBumpSlot = false local noReflectTex = true local noReflectVal = false local hasReflectSlot = false local numMaps = getNumSubTexmaps mat local numVars = RstGetVariableCount mat local texMapNum = 0 local bumpVal = 1.0 local specVal = 1.0 local reflectVal = 1.0 local specChanMult = [1.0, 1.0, 1.0] local alphaBmpVals, specBmpVals, bumpBmpRanges local alphaTexNums = #{} local alphaTexOffset = (numMaps / 2) -- Check variables: for varNum = 1 to numVars do ( local varType = RstGetVariableType mat varNum local varName = RstGetVariableName mat varNum case varType of ( "texmap": ( -- No-Specular/Bump flags are turned OFF if matching bitmaps are found: texMapNum += 1 local texMap = getSubTexmap mat texMapNum local filename = "" local fileExists = False -- Does this slot set a valid filename? if (isKindOf texMap Bitmaptexture) and (texMap.filename != "") do ( filename = texMap.filename fileExists = (doesFileExist filename) if not fileExists do ( append missingFiles filename ) ) case of ( (RsIsDiffuseMap varName): ( hasDiffSlot = True if fileExists do ( if (shaderItem.isAlphaShader) do ( local alphaIdx = (texMapNum + alphaTexOffset) alphaTexNums[alphaIdx] = True ) noDiffTex = False ) ) ((noSpecTex) and (shaderItem.isSpec) and (RsIsSpecMap varName)): ( hasSpecSlot = True if fileExists do ( specBmpVals = getBmpRange texMap.filename maxValue:true noSpecTex = false ) ) ((noBumpTex) and (shaderItem.isBump) and (RsIsBumpMap varName)): ( hasBumpSlot = True if fileExists do ( bumpBmpRanges = getBmpRange texMap.filename maxValue:false rgbaChans:#{1,2} noBumpTex = false ) ) ((noReflectTex) and (shaderItem.isReflect) and (matchPattern varName pattern:"Environment*")): ( hasReflectSlot = True if fileExists do ( noReflectTex = false ) ) ) ) "vector3": ( case varName of ( "specular map intensity mask color": ( specChanMult = RstGetVariable mat varNum ) ) ) "float": ( -- No-Specular/Bump-value flags are turned ON if matching values are zero: case varName of ( "Bumpiness": ( bumpVal = RstGetVariable mat varNum if (bumpVal <= varThreshold) do (noBumpVal = true) ) "Specular Intensity": ( specVal = RstGetVariable mat varNum if (specVal <= varThreshold) do (noSpecVal = true) ) "Reflectivity": ( reflectVal = RstGetVariable mat varNum if (reflectVal <= varThreshold) do (noReflectVal = true) ) ) ) ) ) -- Check diffuse-alphas, if found: if shaderItem.isAlpha do ( for alphaIdx in alphaTexNums while noAlphaTex do ( local texMapNums = #((alphaIdx - alphaTexOffset), (alphaIdx)) for slotNum in #(2,1) while noAlphaTex do ( local texMapNum = texMapNums[slotNum] local texMap = getSubTexmap mat texMapNum if (isKindOf texMap Bitmaptexture) and (texMap.filename != "") do ( local filename = texMap.filename if doesFileExist filename then ( alphaType = getBmpRange filename getAlphaType:True slotNum:slotNum -- Will be set to False if texture was found in slot 2, or if slot 1 provides alpha: noAlphaTex = ((slotNum == 1) and (alphaType == #None)) ) else ( append missingFiles filename ) ) ) -- If Alpha-texture wasn't found, check vert-alphas: local uvChannel_vertAlpha = -2 for item in objFaceMats while noAlphaTex do ( local thisMatFaces = item.matFaces[matNum] -- If this object uses this material, test its vert-alpha: if (thisMatFaces != undefined) and (thisMatFaces.numberSet != 0) do ( local thisObj = item.obj local objOp = RsMeshPolyOp thisObj if (objOp.getMapSupport thisObj uvChannel_vertAlpha) do ( local objGetMapFace = RsGetMapFaceFunc thisObj local objGetMapVert = objOp.getMapVert for faceNum = thisMatFaces while noAlphaTex do ( local alphaVerts = objGetMapFace thisObj uvChannel_vertAlpha faceNum for vertNum in alphaVerts while noAlphaTex do ( local vertVal = objGetMapVert thisObj uvChannel_vertAlpha vertNum -- If vert-alpha is non-white, we no longer want to warn about lack of alpha-texture: if (vertVal[1] != 1) do ( noAlphaTex = False alphaType = #vertex ) ) ) ) ) ) ) ) if (missingFiles.count == 0) then ( local badAlpha = false local badSpec = false local badBump = false local badReflect = false -- Reasons are given a line each: local reasons = #() if shaderItem.isAlpha do ( -- Does material have a suitable alpha-channel? if ((not noAlphaTex) and (alphaType == #none)) do (badAlpha = true; append reasons "Alpha-channel all is all white (or close to it)") if (noAlphaTex and shaderItem.isAlphaShader) do (badAlpha = true; append reasons "Unused diffuse-alpha texture slot.") ) if shaderItem.isSpec do ( -- Does bitmap have all specular-channel multipliers turned down too low? local validSpecMults = false for n = 1 to 3 while not validSpecMults do ( validSpecMults = (specChanMult[n] > varThreshold) ) if not validSpecMults do (badSpec = true; append reasons "Specular-channel multipliers are set to negligible values.") if noSpecVal do (badSpec = true; append reasons "Specular Intensity value is negligible.") -- Check bitmap values, if multipliers are valid: if (not badSpec) and (specBmpVals != undefined) do ( local ineffectiveTex = true local ineffectiveMultTex = true -- Check rgb values: for n = 1 to 3 while (ineffectiveTex or ineffectiveMultTex) do ( local bmpChanVal = specBmpVals[n] local bmpChanMult = specChanMult[n] -- Does this specular-texture have a channel above the threshold? if ineffectiveTex and (bmpChanVal > texThreshold) do ( ineffectiveTex = false ) -- See if the bitmap is still effective once the multipliers are applied, if they were accepted: if ineffectiveMultTex and ((bmpChanVal * bmpChanMult * specVal) > varTexThreshold) do ( ineffectiveMultTex = false ) ) case of ( ineffectiveTex:(badSpec = true; append reasons "Specular Texture is too dark to be effective.") ineffectiveMultTex:(badSpec = true; append reasons "Specular Texture's values are ineffective once multiplier-values are applied.") ) ) if (hasSpecSlot and noSpecTex) do ( badSpec = true; append reasons "Unused Specular Texture slot." ) ) if shaderItem.isBump do ( if noBumpTex do (badBump = true; append reasons "Unused Bump Texture slot.") if noBumpVal do (badBump = true; append reasons "Bumpiness value is negligible.") -- Check bitmap values, if multipliers are valid: if (not badBump) and (bumpBmpRanges != undefined) do ( local ineffectiveTex = true local ineffectiveMultTex = true -- Check red/green ranges: for n = 1 to 2 while (ineffectiveTex or ineffectiveMultTex) do ( local bmpChanVal = bumpBmpRanges[n] -- Does this bump-texture have a channel-range above the threshold? if ineffectiveTex and (bmpChanVal > texThreshold) do ( ineffectiveTex = false ) -- See if the bitmap is still effective once the multipliers are applied, if they were accepted: if ineffectiveMultTex and ((bmpChanVal * bumpVal) > varTexThreshold) do ( ineffectiveMultTex = false ) ) case of ( ineffectiveTex:(badBump = true; append reasons "Bump Texture's value-ranges are too small to be effective.") ineffectiveMultTex:(badBump = true; append reasons "Bump Texture's value-ranges are too small to be effective once Bumpiness multiplier is applied.") ) ) ) if shaderItem.isReflect do ( if noReflectVal then (badReflect = true; append reasons "Reflectivity value is negligible.") else ( if (not noReflectTex) do ( append suggestionList (matSuggest action:"Check suitability?\nThis is mainly used for interiors" mat:mat reason:"Uses Environment Texture" ticked:false) ) ) ) --format "%: % (%) badSpec:% badBump:% badAlpha:% badReflect:%\n" mat.name matShaderName lookupNum badSpec badBump badAlpha badReflect -- See if shader has a suggested alternative name, if it's missing texturemaps: local suggestedName local testList = #( datapair test:(badSpec and badBump and badAlpha and badReflect) val:(shaderItem.noBumpSpecAlphaReflect), datapair test:(badSpec and badBump and badReflect) val:(shaderItem.noBumpSpecReflect), datapair test:(badSpec and badAlpha and badReflect) val:(shaderItem.noSpecAlphaReflect), datapair test:(badBump and badAlpha and badReflect) val:(shaderItem.noBumpAlphaReflect), datapair test:(badSpec and badReflect) val:(shaderItem.noSpecReflect), datapair test:(badBump and badReflect) val:(shaderItem.noBumpReflect), datapair test:(badAlpha and badReflect) val:(shaderItem.noAlphaReflect), datapair test:(badReflect) val:(shaderItem.noReflect), datapair test:(badSpec and badBump and badAlpha) val:(shaderItem.noBumpSpecAlpha), datapair test:(badSpec and badBump) val:(shaderItem.noBumpSpec), datapair test:(badSpec and badAlpha) val:(shaderItem.noSpecAlpha), datapair test:(badBump and badAlpha) val:(shaderItem.noBumpAlpha), datapair test:(badSpec) val:(shaderItem.noSpec), datapair test:(badBump) val:(shaderItem.noBump), datapair test:(badAlpha) val:(shaderItem.noAlpha) ) for item in testList where item.test while (suggestedName == undefined) do ( suggestedName = item.val ) -- If possible, swap alpha out for cutout shader: if shaderItem.isAlpha and (not shaderItem.isCutout) and (alphaType == #Cutout) do ( local checkName = if (suggestedName == undefined) then matShaderName else suggestedName local alphaIdx = findString checkName "alpha" -- Generate alternative shader-name with "cutout" replacing "alpha": local newNameStart = (substring checkName 1 (alphaIdx - 1)) local newNameEnd = (substring checkName (alphaIdx + 5) -1) local newSuggestion = (newNameStart + "cutout" + newNameEnd) -- Only swap for this name if it's on our list: if (findItem shaderLookupListNames newSuggestion) != 0 do ( suggestedName = newSuggestion append reasons "Diffuse-alpha contains only black/white (or near enough) values" ) ) -- Generate change-reason string: local reason = stringStream "" for n = 1 to reasons.count do ( format "%" reasons[n] to:reason if (n != reasons.count) do ( format "\n" to:reason ) ) if (suggestedName != undefined) do ( local action = ("Change shader: \n'" + matShaderName + "' -> '" + suggestedName + "'") append suggestionList (matSuggest mat:mat action:action reason:(reason as string) func:RstSetShaderName arg:suggestedName) ) ) else ( for filename in missingFiles do ( local msg = "Missing file: " + filename append suggestionList (matSuggest action:"Sync/find textures" mat:mat reason:msg ticked:false) ) ) ) ) ) ) format "Material-processing time: %s\n" ((timeStamp() - timeStart) / 1000.0) progressEnd() if (not notCancelled) do (return false) if (suggestionList.count == 0) then ( messageBox "No auto-optimisable materials were found.\n\nHooray!" title:"No optimisations found" beep:false ) else ( local queryListLabels = #(" ", "Material", "Warning", "Recommended Action") local queryList = for item in suggestionList collect #(item.mat.name, item.reason, item.action) local queryListTicks = #{} local queryVal = RsQueryBoxMultiBtn "Possible material-optimisations were found.\n\nDo you want to perform these changes?\n\n(double-click to open in Material Editor)" title:"Change materials?" \ timeout:-1 width:800 labels:#("Perform ticked actions", "Cancel") tooltips:#("Perform the suggested material optimisations ticked on the list") \ listItems:queryList listTitles:queryListLabels listCheckStates:queryListTicks listDoubleClickFunc:listDoubleClick if (queryVal == 1) do ( -- Perform ticked actions: for itemNum = queryListTicks do ( local actionItem = suggestionList[itemNum] if (actionItem.func != undefined) do ( actionItem.func actionItem.mat actionItem.arg ) ) ) ) -- Clear suggestion-list: suggestionList.count = 0 return OK ) on btnCheckForNonoptimal pressed do ( checkForNonOptimal() setFocus RsShaderOptimiserRoll ) on btnOpenCSV pressed do ( local isCheckedOut = (gRsPerforce.checkedOutForEdit #(listFilename))[1] if (not isCheckedOut) and (querybox "Check out file before editing?" title:"Perforce checkout") do ( gRsPerforce.edit listFilename silent:true ) -- Create a new Excel instance, and use it to open the CSV: local excel = CreateOLEObject "Excel.Application" excel.application.Workbooks.Open listFilename excel.ActiveSheet.Name = "Shader Alternatives" local worksheet = excel.ActiveSheet -- Set up column-filters: workSheet.EnableAutoFilter = True workSheet.Cells.AutoFilter 1 -- Freeze header-rows/columns, and set to bold: (workSheet.Range "B2" "B2").Select() excel.ActiveWindow.FreezePanes = True (workSheet.Range "A1" "A1").EntireRow.Font.Bold = True (workSheet.Range "A1" "A1").EntireColumn.Font.Bold = True -- Adjust all columns: workSheet.Columns.AutoFit(); -- Show Excel, then give it focus: excel.visible = True SC_MINIMIZE = 0xF020 SC_RESTORE = 0xF120 WM_SYSCOMMAND = 0x112 windows.SendMessage excel.Hwnd WM_SYSCOMMAND SC_MINIMIZE 0 windows.SendMessage excel.Hwnd WM_SYSCOMMAND SC_RESTORE 0 releaseOLEObject excel ) on RsShaderOptimiserRoll open do ( RsShaderOptimiserRoll.height = btnOpenCSV.pos.Y + 35 banner.setup() -- Make sure we have latest list-file (if it's not writable) if (not (doesFileExist listFilename)) or (getfileattribute listFilename #readOnly) do ( pushPrompt ("Syncing list-file: "+ (RsMakeBackSlashes listFilename)) gRsPerforce.sync listFilename silent:true popPrompt() ) ) on RsShaderOptimiserRoll close do ( --print "CLOSING OPTIMISER" ) ) createDialog RsShaderOptimiserRoll