-- Renders all the available render targets of the loaded project. -- Asks the user for an output directory and saves all the images into -- that directory (_01.png, _02.png, ...) -- -- @author Thomas, Mark -- @version 0.2 -- @shortcut ctrl + b -- Start with a clean slate -- octane.project.reset() local version = " v1.0 - for OctaneRender 1.5" local settings = { ["projectPath"] = octane.project.getCurrentProject(), ["renderTargets"] = octane.project.getSceneGraph():findNodes(octane.NT_RENDERTARGET), ["maxSamples"] = 1000, ["outputDirectory"] = nil, } -- If no render targets are found, error out if #settings.renderTargets == 0 then error("No render targets were found in this project.", projectPath) end blnUseUI = true if blnUseUI == true then -- User Interface Code --------------------------------------------------------------------------------------------------------- -- Gui Helpers -- Creates a text label and returns it function createLabel(text, width, height) return octane.gui.create { type = octane.gui.componentType.LABEL, -- type of the component text = text, -- text that appears on the label width = width, -- width of the label in pixels height = height, -- height of the label in pixels } end -- Creates a button and returns it function createButton(name, text, width, height, tooltip) return octane.gui.create { type = octane.gui.componentType.BUTTON, -- type of the component name = name, -- name of the component text = text, -- button text width = width, -- width in pixels height = height, -- height in pixels tooltip = tooltip, -- tooltip text } end -- Creates a slider and returns it function createSlider(name, value, min, max, step, width, height, logarithmic) return octane.gui.create { type = octane.gui.componentType.SLIDER, -- type of the component name = name, -- name of the component value = value, -- value of the slider minValue = min, -- minimum value of the slider maxValue = max, -- maximum value of the slider step = step, -- interval between 2 discrete slider values width = width, -- width of the slider in pixels height = height, -- height of the slider in pixels logarithmic = logarithmic, -- Make slider logarithmic } end -- Creates a group and retuns it function createGroup(name, children, border, rows, cols, width, height, text, padding, inset, center) return octane.gui.create { type = octane.gui.componentType.GROUP, -- type of the component name = name, -- name of component children = children, -- list of children components border = border, -- boolean flag to show border rows = rows, -- number of rows in group grid cols = cols, -- number of cols in group grid width = width, -- width in pixels height = height, -- height in pixels text = text, -- text to display at top of group padding = padding, -- internal padding in each cell inset = inset, -- inset of the group component centre = center, -- center the group } end -- Gui Creation -- Table of Render Targets --tblRenderTargets = octane.gui.create --{ --type = octane.gui.componentType.TABLE, --name = "rt", --enable = true, --header = {"Render Target Name"} , --width = 300, --height = 50, --items = settings.rendertargets, --} -- Maximum Samples Slider lblMaxSamples = createLabel("Maximum Samples", 100, 20) sldMaxSamples = createSlider("ms", 1000, 0, 16000 , 50, 380, 20, true) -- Output Location btnOutput = createButton("output", "Output Folder...", 100, 20, "Specify the folder to receive the rendered output files.") -- Maximum Samples Slider lblBlank = createLabel("", 5, 20) lblCurrentRenderTarget = createLabel("Processing Render Target:", 380, 20) -- Create an editor that will show the chosen file path txtEditorDirectory = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "" , x = 0 , width = 374 , height = 20 , enable = false , } -- Buttons (Apply not used in this case) btnOK = createButton("ok", "Apply", 100, 20, "Start batch processing.") btnCancel = createButton("cancel", "Cancel", 100, 20, "Cancel.") grpExecute = createGroup("grpExecute", { btnOK, btnCancel}, false, 1, 2, nil, nil, "Execute", { 5 }, nil, false) -- Settings group children Settings_children = { lblMaxSamples , sldMaxSamples, btnOutput , txtEditorDirectory, lblBlank , lblCurrentRenderTarget, } grpSettings = createGroup("Settings_group", Settings_children, false, 3, 2, 600, 20, "Settings", { 5 }, { 5 }) -- Overall layout group children layout_children = { grpSettings, grpExecute, } layoutGrp = createGroup("layout", layout_children, false, 2, 1, nil, nil, "", { 2 }, nil, true) -- window that holds all components wndMain = octane.gui.create { type = octane.gui.componentType.WINDOW, text = "Batch Process All Render Targets " .. version , children = { layoutGrp }, width = grpSettings:getProperties().width, height = grpSettings:getProperties().height + grpExecute:getProperties().height + 25 } end -- Initialise GUI btnCancel:updateProperties{ enable = false} -- Callback function for the GUI Controls local function guiCallback(component, event) if component == sldMaxSamples then settings.maxSamples = sldMaxSamples:getProperties().value elseif component == btnOutput then -- ask the user for an output directory for the results local ret = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Select output directory for the render results", path = octane.file.getParentDirectory(settings.projectPath), browseDirectory = true, save = false, } if not ret.result or ret.result == "" then error("no output directory selected") end settings.outputDirectory = ret.result txtEditorDirectory.text = settings.outputDirectory elseif component == btnOK then -- Clear the cancel flag IS_CANCELLED = false btnCancel:updateProperties{ enable = true} btnOK:updateProperties{ enable = false} btnOutput:updateProperties{ enable = false} txtEditorDirectory:updateProperties{ enable = false} sldMaxSamples:updateProperties{ enable = false} execute() elseif component == btnCancel then cancelRender() wndMain:closeWindow() elseif component == wndMain then cancelRender() end end -- Hookup GUI controls to their Callback function sldMaxSamples:updateProperties { callback = guiCallback } btnOutput:updateProperties { callback = guiCallback } btnOK:updateProperties { callback = guiCallback } btnCancel:updateProperties { callback = guiCallback } -- End User Interface Code -------------------------------------------------------------------------------------------------------- function cancelRender() IS_CANCELLED = true octane.render.callbackStop() -- octane.render.continue() -- octane.render.restart() end ------------------------------------------------------------------------------------------------------------------------------------ -- MAIN ROUTINE ------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------ function execute() local BatchStartTime = os.clock() -- loop over all the render targets and render them for ix, renderTarget in ipairs(settings.renderTargets) do -- Break out if we're cancelled and set it in the progress bar if IS_CANCELLED then break end -- print(string.format("render project '%s', render target '%s' with %d s/px", settings.projectPath, renderTarget.name, settings.maxSamples)) lblCurrentRenderTarget:updateProperties{ text = "Processing Render Target: '" .. renderTarget.name .. "' - " .. ix .. " of " .. #settings.renderTargets} local ImageStartTime = os.clock() -- Render the image octane.render.start { renderTargetNode = renderTarget, maxSamples = settings.maxSamples, } local ImageElapsedTime = string.format("%.02f", os.clock() - ImageStartTime) -- Create an output path for the image local outImage = string.format("%s/" .. renderTarget.name .. "_" .. settings.maxSamples .." samples_" .. ImageElapsedTime .." secs_%02d.png", settings.outputDirectory, ix) -- Save out the image octane.render.saveImage(outImage, octane.render.imageType.PNG8) -- print("saved out render result:", outImage) end local BatchElapsedTime = string.format("%.02f", os.clock() - BatchStartTime) lblCurrentRenderTarget:updateProperties{ text = "Processing Complete: " .. #settings.renderTargets .. " Image Files ouput in " .. BatchElapsedTime .. " secs."} end -- This must be placed such that all required functions are read prior to execution. -- The script will hold here until the window closes wndMain:showWindow()