-- Create a mix material diffuse/glossy. -- -- @description Create a mix-organic material from various RGBA Images -- @author Beppe Gullotta aka "bepeg4d" with the great help of Thomas "stratified" Loockx local version = "1.0" -- @shortcut ctrl + 4 -- This script create a node-group with a mix, a diffuse and a glossy material -- with various imput nodes derived from one or more RGBA textures. -- GUI code -- creates a note text and returns it local function createLabel(text) return octane.gui.create { type = octane.gui.componentType.LABEL, -- type of component text = text, -- text that appears on the label width = 624, -- width of the label in pixels height = 24, -- height of the label in pixels } end -- lets create a note label local noteLbl1 = createLabel("Fill all the paths with the proper texture image to automatically create a mix between a diffuse and a glossy material") local noteLbl2 = createLabel("At least a Diffuse texture is needed, if the other paths are not specified, it will be used for all the other channels") local noteLbl3 = createLabel("except for the Opacity texture that has several options, if an Opacity path is not chosen, the material is opaque.") -- for layouting the notes we use a group local noteGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 3, cols = 1, children = { noteLbl1, noteLbl2, noteLbl3, }, padding = { 2 }, inset = { 5 }, } -- helper to pop-up an error dialog and optionally halts the script local function showError(text, halt) octane.gui.showDialog { type = octane.gui.dialogType.BUTTON_DIALOG, title = "Create Mix Material Error", text = text, icon = octane.gui.dialogIcon.WARNING, } if halt then error("ERROR: "..text) end end -- DIFFUSE -- -- create a button to show a file chooser for Diffuse texture local fileChooseButtonD = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Choose path", width = 80, height = 20, } -- create an editor that will show the chosen file path for Diffuse texture local fileEditorD = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", x = 20, width = 410, height = 20, enable = true, } -- create a label local noteLblD1 = octane.gui.create { type = octane.gui.componentType.LABEL, text = "suffix", width = 40, } -- create a text field for the suffix to add at the end of the name of the material local fileTextD = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", width = 60, height = 20, enable = true, } -- for layouting all the elements for the Diffuse texture we use a group local difGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Diffuse Texture", rows = 1, cols = 4, children = { fileChooseButtonD, fileEditorD, noteLblD1, fileTextD, }, padding = { 2 }, inset = { 5 }, } -- TRANSMISSION -- -- create the checkbox for activate/deactivate the transmission channel local fileCheckBoxT = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "If checked, the Transmission node is created based on the Diffuse texture.", width = 625, } -- for layouting the check-box for the Transmission option we use a group local trnGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Transmission", rows = 1, cols = 1, children = { fileCheckBoxT, }, padding = { 2 }, inset = { 5 }, } -- OPACITY -- -- create a combo-box to choose the type for the Opacity texture local fileComboBoxO = octane.gui.create { type = octane.gui.componentType.COMBO_BOX, text = "Image type", width = 100, height = 20, items = { "Alpha Image", "Grayscale Image", "Derive from Diffuse", }, selectedIx = 1 } -- create a button to show a file chooser for Opacity texture local fileChooseButtonO = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Choose path", width = 80, height = 20, } -- create an editor that will show the chosen file path for Opacity texture local fileEditorO = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", x = 20, width = 365, height = 20, enable = true, } -- create the checkbox for inverting the opacity texture local fileCheckBoxO = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Invert", width = 50, checked = false, } -- for layouting all the elements for the Opacity texture we use a group local opaGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Opacity Texture", rows = 1, cols = 4, children = { fileComboBoxO, fileChooseButtonO, fileEditorO, fileCheckBoxO, }, padding = { 2 }, inset = { 5 }, } -- BUMP/NORMAL -- -- create the compo-box for choose between bump and normal channel local fileComboBoxB = octane.gui.create { type = octane.gui.componentType.COMBO_BOX, text = "Bump/Normal", width = 100, height = 20, items = { "Bump", "Normal", }, selectedIx = 1 } -- create a button to show a file chooser for Bump texture local fileChooseButtonB = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Choose path", width = 80, height = 20, } -- create an editor that will show the chosen file path for Bump texture local fileEditorB = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", x = 20, width = 365, height = 20, enable = true, } -- create the checkbox for inverting the bump/normal texture local fileCheckBoxB = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Invert", width = 50, checked = false, } -- for layouting all the elements for the Bump texture we use a group local bumpGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Bump/Normal Texture", rows = 1, cols = 4, children = { fileComboBoxB, fileChooseButtonB, fileEditorB, fileCheckBoxB, }, padding = { 2 }, inset = { 5 }, } -- SPECULAR/ROUGHNESS -- -- create a button to show a file chooser for Specular/Roughness texture local fileChooseButtonR = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Choose path", width = 80, height = 20, } -- create an editor that will show the chosen file path for Specular/Roughness texture local fileEditorR = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", x = 20, width = 465, height = 20, enable = true, } -- create the checkbox for inverting the Specular/Roughness texture local fileCheckBoxR = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Invert", width = 50, checked = false, } -- for layouting all the elements for the Specular/Roughness texture we use a group local rougGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Specular/Roughness Texture", rows = 1, cols = 3, children = { fileChooseButtonR, fileEditorR, fileCheckBoxR, }, padding = { 2 }, inset = { 5 }, } -- BUTTONS -- local createButton = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Create", width = 160, height = 20, tooltip = "Create the material with the choosed textures", } local exitButton = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Exit", width = 160, height = 20, tooltip = "Exit from the script without creating the material", } local buttonGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 1, cols = 2, children = { createButton, exitButton, }, padding = { 5 }, border = false, } -- group that layouts the other groups local layoutGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 7, cols = 1, children = { noteGrp, difGrp, trnGrp, opaGrp, bumpGrp, rougGrp, buttonGrp, }, centre = true, padding = { 2 }, border = false, debug = false, -- true to show the outlines of the group, handy } -- window that holds all components local MixMatWindow = octane.gui.create { type = octane.gui.componentType.WINDOW, text = "Create a Mix with a Diffuse and a Glossy Material v"..version, children = { layoutGrp }, width = layoutGrp:getProperties().width, height = layoutGrp:getProperties().height, } --------------------------end-gui--------------------------------------- -- enable all the ui fileChooseButtonD:updateProperties{ enable = true} fileTextD :updateProperties{ enable = true} fileCheckBoxT :updateProperties{ enable = true} fileChooseButtonO:updateProperties{ enable = true} fileCheckBoxO :updateProperties{ enable = true} fileChooseButtonB:updateProperties{ enable = true} fileCheckBoxB :updateProperties{ enable = true} fileChooseButtonR:updateProperties{ enable = true} fileCheckBoxR :updateProperties{ enable = true} createButton :updateProperties{ enable = false } exitButton :updateProperties{ enable = true } --button functions local function createGraph() MixMatWindow:closeWindow(true) end local function exitAndDontCreateGraph() MixMatWindow:closeWindow(false) end -- global variable that holds the input path IND_PATH = nil INO_PATH = nil INB_PATH = nil INR_PATH = nil -- callback handling the GUI elements local function guiCallback(component, event) -- FILE CHOOSING -- if component == fileChooseButtonD then -- choose an input RGB file local resDif = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Choose the texture image for the diffuse channel", wildcards = "*.jpg; *.png; *.psd; *.tif; *.tga", save = false } -- if a file is chosen if resDif.result ~= "" then fileEditorD:updateProperties{ text = resDif.result } IND_PATH = resDif.result createButton:updateProperties{ enable = true } else createButton:updateProperties{ enable = false } exitButton:updateProperties{ enable = true } fileEditorD:updateProperties{ text = "" } IND_PATH = nil end print("Loading D texture: ", IND_PATH) -- OPACITY -- elseif component == fileChooseButtonO then local resOpa = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Choose the RGBA texture image for the opacity channel", wildcards = "*.jpg; *.png; *.psd; *.tif; *.tga", save = false } -- if a file is chosen if resOpa.result ~= "" then fileEditorO:updateProperties{ text = resOpa.result } INO_PATH = resOpa.result else fileEditorO:updateProperties{ text = "" } INO_PATH = nil end print("Loading O texture: ", INO_PATH) -- BUMP -- elseif component == fileChooseButtonB then local resBum = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Choose the texture image for the bump channel", wildcards = "*.jpg; *.png; *.psd; *.tif; *.tga", save = false } -- if a file is chosen if resBum.result ~= "" then fileEditorB:updateProperties{ text = resBum.result } INB_PATH = resBum.result else fileEditorB:updateProperties{ text = "" } INB_PATH = nil end print("Loading B texture: ", INB_PATH) -- SPECULAR/ROUGHNESS -- elseif component == fileChooseButtonR then local resRug = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Choose the texture image for the specular/roughness channels", wildcards = "*.jpg; *.png; *.psd; *.tif; *.tga", save = false } -- if a file is chosen if resRug.result ~= "" then fileEditorR:updateProperties{ text = resRug.result } INR_PATH = resRug.result else fileEditorR:updateProperties{ text = "" } INR_PATH = nil end print("Loading S texture: ", INR_PATH) -- close -- elseif component == createButton then createGraph() elseif component == exitButton then exitAndDontCreateGraph() elseif component == MixMatWindow then -- when the window closes, print something if event == octane.gui.eventType.WINDOW_CLOSE then print ("Let's go!") end end end -- hookup the callback with all the GUI elements fileChooseButtonD:updateProperties { callback = guiCallback } fileCheckBoxT :updateProperties { callback = guiCallback } fileTextD :updateProperties { callback = guiCallback } fileChooseButtonO:updateProperties { callback = guiCallback } fileCheckBoxO :updateProperties { callback = guiCallback } fileChooseButtonB:updateProperties { callback = guiCallback } fileCheckBoxB :updateProperties { callback = guiCallback } fileChooseButtonR:updateProperties { callback = guiCallback } fileCheckBoxR :updateProperties { callback = guiCallback } createButton:updateProperties { callback = guiCallback } exitButton:updateProperties { callback = guiCallback } MixMatWindow:updateProperties { callback = guiCallback } -- the script will block here until the window closes local create = MixMatWindow:showWindow() -- stop the script if the user clicked the exit button if not create then return end -- table with params for the "macro" node graph params = {} params.type = octane.GT_STANDARD params.name = "tex-name" params.position = {200, 200} -- creates the "macro" node graph, ie. root for all other nodes root = octane.nodegraph.create(params) -- creates two materials and a material mix node diffuse = octane.node.create{type=octane.NT_MAT_DIFFUSE, name="Diffuse", graphOwner=root, position={300, 250}} glossy = octane.node.create{type=octane.NT_MAT_GLOSSY, name="Glossy", graphOwner=root, position={600, 250}} matMix = octane.node.create{type=octane.NT_MAT_MIX, name="Mix", graphOwner=root, position={450, 300}} -- creates some texture input nodes and assign some names inColor1 = octane.node.create{type=octane.NT_IN_TEXTURE,name="Color", graphOwner=root, position={200, 100}} --inColor2 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Bump", graphOwner=root, position={450, 150}} inColor3 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Trans-Color", graphOwner=root, position={300, 100}} inColor4 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Specular", graphOwner=root, position={500, 100}} inColor5 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Opacity", graphOwner=root, position={450, 200}} inColor6 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Amount", graphOwner=root, position={50, 300}} inColor7 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Roughness", graphOwner=root, position={550, 150}} inColor8 = octane.node.create{type=octane.NT_IN_FLOAT, name="Index", graphOwner=root, position={650, 150}} -- creates an image texture node for diffuse channel inColor1Node = octane.node.create{type= octane.NT_TEX_IMAGE, pinOwnerNode=inColor1, pinOwnerId=octane.P_INPUT} inColor1Node:setAttribute(octane.A_FILENAME, IND_PATH) -- creates the conditions for bump/normal channel if INB_PATH == nil and fileComboBoxB.selectedIx == 1 then INB_PATH = IND_PATH inColor2 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Bump", graphOwner=root, position={450, 150}} inColor2Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor2, pinOwnerId=octane.P_INPUT} inColor2Node:setAttribute(octane.A_FILENAME, INB_PATH) inColor2Node:setPinValue(octane.P_POWER, {0.3, 0.3, 0.3}) glossy:connectTo(octane.P_BUMP, inColor2) diffuse:connectTo(octane.P_BUMP, inColor2) elseif INB_PATH == nil and fileComboBoxB.selectedIx == 2 then INB_PATH = IND_PATH inColor2 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Bump", graphOwner=root, position={450, 150}} inColor2Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor2, pinOwnerId=octane.P_INPUT} inColor2Node:setAttribute(octane.A_FILENAME, INB_PATH) inColor2Node:setPinValue(octane.P_POWER, {0.3, 0.3, 0.3}) glossy:connectTo(octane.P_BUMP, inColor2) diffuse:connectTo(octane.P_BUMP, inColor2) elseif INB_PATH ~= nil and fileComboBoxB.selectedIx == 1 then inColor2 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Bump", graphOwner=root, position={450, 150}} inColor2Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor2, pinOwnerId=octane.P_INPUT} inColor2Node:setAttribute(octane.A_FILENAME, INB_PATH) inColor2Node:setPinValue(octane.P_POWER, {0.3, 0.3, 0.3}) glossy:connectTo(octane.P_BUMP, inColor2) diffuse:connectTo(octane.P_BUMP, inColor2) elseif INB_PATH ~= nil and fileComboBoxB.selectedIx == 2 then inColor2 = octane.node.create{type=octane.NT_IN_TEXTURE, name="Normal", graphOwner=root, position={450, 150}} inColor2Node = octane.node.create{type= octane.NT_TEX_IMAGE, pinOwnerNode=inColor2, pinOwnerId=octane.P_INPUT} inColor2Node:setAttribute(octane.A_FILENAME, INB_PATH) inColor2Node:setPinValue(octane.P_POWER, {0.75, 0.75, 0.75}) glossy:connectTo(octane.P_NORMAL, inColor2) diffuse:connectTo(octane.P_NORMAL, inColor2) end if fileCheckBoxB.checked == true then inColor2Node:setPinValue(octane.P_INVERT, true) elseif fileCheckBoxB.checked == false then inColor2Node:setPinValue(octane.P_INVERT, false) end -- creates the conditions for transmission channel if fileCheckBoxT.checked == true then inColor3Node = octane.node.create{type= octane.NT_TEX_IMAGE, pinOwnerNode=inColor3, pinOwnerId=octane.P_INPUT} inColor3Node:setAttribute(octane.A_FILENAME, IND_PATH) inColor3Node:setPinValue(octane.P_GAMMA, 1.6) elseif fileCheckBoxT.checked == false then inColor3Node = octane.node.create{type= octane.NT_TEX_FLOAT, pinOwnerNode=inColor3, pinOwnerId=octane.P_INPUT} inColor3Node:setAttribute(octane.A_VALUE, 0) end -- creates the conditions for specular channel if INR_PATH ~= nil then inColor4Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor4, pinOwnerId=octane.P_INPUT} inColor4Node:setAttribute(octane.A_FILENAME, INR_PATH) elseif INR_PATH == nil then INR_PATH = IND_PATH inColor4Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor4, pinOwnerId=octane.P_INPUT} inColor4Node:setAttribute(octane.A_FILENAME, INR_PATH) end if fileCheckBoxR.checked == true then inColor4Node:setPinValue(octane.P_INVERT, true) elseif fileCheckBoxR.checked == false then inColor4Node:setPinValue(octane.P_INVERT, false) end -- creates the conditions for opacity channel if INO_PATH == nil and fileComboBoxO.selectedIx ~= 3 then inColor5Node = octane.node.create{type= octane.NT_TEX_FLOAT, pinOwnerNode=inColor5, pinOwnerId=octane.P_INPUT} inColor5Node:setAttribute(octane.A_VALUE, 1) elseif fileComboBoxO.selectedIx == 1 then inColor5Node = octane.node.create{type= octane.NT_TEX_ALPHAIMAGE, pinOwnerNode=inColor5, pinOwnerId=octane.P_INPUT} inColor5Node:setAttribute(octane.A_FILENAME, INO_PATH) elseif fileComboBoxO.selectedIx == 2 then inColor5Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor5, pinOwnerId=octane.P_INPUT} inColor5Node:setAttribute(octane.A_FILENAME, INO_PATH) elseif INO_PATH == nil and fileComboBoxO.selectedIx == 3 then INO_PATH = (IND_PATH) inColor5Node = octane.node.create{type= octane.NT_TEX_ALPHAIMAGE, pinOwnerNode=inColor5, pinOwnerId=octane.P_INPUT} inColor5Node:setAttribute(octane.A_FILENAME, INO_PATH) end if inColor5Node.type ~= octane.NT_TEX_FLOAT and fileCheckBoxO.checked == true then inColor5Node:setPinValue(octane.P_INVERT, true) elseif inColor5Node.type ~= octane.NT_TEX_FLOAT and fileCheckBoxO.checked == false then inColor5Node:setPinValue(octane.P_INVERT, false) end -- creates the conditions for roughness channel if INR_PATH ~= nil then inColor7Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor7, pinOwnerId=octane.P_INPUT} inColor7Node:setAttribute(octane.A_FILENAME, INR_PATH) elseif INR_PATH == nil then INR_PATH = IND_PATH inColor7Node = octane.node.create{type= octane.NT_TEX_FLOATIMAGE, pinOwnerNode=inColor7, pinOwnerId=octane.P_INPUT} inColor7Node:setAttribute(octane.A_FILENAME, INR_PATH) end if fileCheckBoxR.checked == true then inColor7Node:setPinValue(octane.P_INVERT, true) elseif fileCheckBoxR.checked == false then inColor7Node:setPinValue(octane.P_INVERT, false) end --set the name of the material as the name of the texture and add the suffix fileName = octane.file.getFileNameWithoutExtension(IND_PATH) print (fileName) fileName= fileName.gsub(fileName, '_.+', "") fileName=(fileName .. fileTextD.text) print (fileName) root : updateProperties{name=fileName} --set the name of all the nodes as the name of the texture with the type suffix diffuse:updateProperties{name=fileName.."-D"} glossy:updateProperties{name=fileName.."-G"} matMix:updateProperties{name=fileName.."-Mix"} -- creates the output pin, material type outMat = octane.node.create{type=octane.NT_OUT_MATERIAL, name=fileName, graphOwner=root, position={450, 350}} --crate the float node for the ammount and index channels inColor6Node = octane.node.create{type=octane.NT_TEX_FLOAT, pinOwnerNode=inColor6, pinOwnerId=octane.P_INPUT} inColor8Node = octane.node.create{type=octane.NT_FLOAT, pinOwnerNode=inColor8, pinOwnerId=octane.P_INPUT} -- change some attributes for some nodes inColor1Node:setPinValue(octane.P_POWER, {0.9, 0.9, 0.9}) inColor4Node:setPinValue(octane.P_GAMMA, 1.6) inColor6Node:setAttribute(octane.A_VALUE, {0.5, 0.5, 0.5}) inColor7Node:setPinValue(octane.P_GAMMA, 3.2) inColor8Node:setAttribute(octane.A_VALUE, {1.3, 1.3, 1.3}) -- connects everything diffuse:connectTo(octane.P_DIFFUSE, inColor1) diffuse:connectTo(octane.P_TRANSMISSION, inColor3) --diffuse:connectTo(octane.P_BUMP, inColor2) diffuse:connectTo(octane.P_OPACITY, inColor5) glossy:connectTo(octane.P_DIFFUSE, inColor1) --glossy:connectTo(octane.P_BUMP, inColor2) glossy:connectTo(octane.P_SPECULAR, inColor4) glossy:connectTo(octane.P_ROUGHNESS, inColor7) glossy:connectTo(octane.P_OPACITY, inColor5) glossy:connectTo(octane.P_INDEX, inColor8) matMix:connectTo(octane.P_MATERIAL1, diffuse) matMix:connectTo(octane.P_MATERIAL2, glossy) matMix:connectTo(octane.P_AMOUNT, inColor6) outMat:connectTo(octane.P_INPUT, matMix) --reorganize all the nodes in the graph octane.nodegraph.unfold(root, false) print ("Goodbye")