-- Create an animated Tiled Camera Node Graph from selection. -- -- @description Create an animated Tiled Camera Node Graph from a selected Render Target node -- @author Beppe Gullotta local version = "1.03" ------------------------------------------ -- dialogs and functions -- local function showError(text) octane.gui.showDialog{ type = octane.gui.dialogType.BUTTON_DIALOG, title = "Error", text = text } end local function showInfo(text) octane.gui.showDialog{ type = octane.gui.dialogType.BUTTON_DIALOG, title = "INFO", icon = 2, text = text } end local function showResults(text) octane.gui.showDialog{ type = octane.gui.dialogType.BUTTON_DIALOG, title = "RESULT", icon = 4, text = text } end local function Reverse (arr) local i, j = 1, #arr while i < j do arr[i], arr[j] = arr[j], arr[i] i = i + 1 j = j - 1 end end local function resCheck(renderT) specialDir = octane.file.getSpecialDirectories() ORpath = string.gsub(specialDir.currentExecutableFile, "%W", "") version2 = string.find(ORpath, "OctaneRender2") version3 = string.find(ORpath, "OctaneRender3") version4 = string.find(ORpath, "OctaneRender4") if version2 ~= nil then NoPano = true filmResT = octane.node.getInputNode(renderT, octane.P_RESOLUTION) else if version3 ~= nil or version4 ~= nil then filmT = octane.node.getInputNode(renderT, octane.P_FILM_SETTINGS) if filmT ~= nil then filmResT = octane.node.getInputNode(filmT, octane.P_RESOLUTION) end end return filmResT, NoPano end end local function walk(node, list) -- recursive walk for i = 1,node:getPinCount() do local src = node:getInputNodeIx(i) if src ~= nil then walk(src, list) end end -- see if this node has a filename attribute local ok, value = pcall(node.getAnimator, node, octane.A_VALUE) if ok then table.insert(list, value) end end ------------------------------------------ local rt = nil local tileMode = nil -- figure out the selection of the Render Target local selection = octane.project.getSelection() if #selection >= 2 then showError("Only one Render target node need to be selected") return elseif #selection == 0 then showError("Please, select a Render target node to run this script") return end if selection[1]:getProperties().type == octane.NT_RENDERTARGET then rt = selection[1] elseif selection[1]:getProperties().type ~= octane.NT_RENDERTARGET and selection[1]:getProperties().pinOwnerNode == nil then showError("Please, select a Render target node") return elseif selection[1]:getProperties().type ~= octane.NT_RENDERTARGET and selection[1]:getProperties().pinOwnerNode.type == octane.NT_RENDERTARGET then rt = selection[1]:getProperties().pinOwnerNode else showError("Please, select a Render target node") return end -- figure out what kind of Camera node is selected cam = rt:getInputNode(octane.P_CAMERA) resCheck(rt) if filmResT == nil then showError("The Resolution node is missing") return end if cam == nil then showError("The Camera node is missing") return elseif cam.type == octane.NT_CAM_BAKING then showError("A Render target with Thin Lens or Panoramic Camera node need to be selected") return elseif cam.type == octane.NT_CAM_PANORAMIC and NoPano == true then showError("With v2, only a Render target with Thin Lens Camera node can be selected") return elseif cam.type == octane.NT_CAM_PANORAMIC and NoPano ~= true then tileMode = 1 elseif cam.type == octane.NT_CAM_THINLENS then tileMode = 2 end -- figure out if the scene is already animated, and show an info or an error message if true list = {} walk(rt, list) if #list ~= 0 then showError(string.format("the selected Render Target has already %s animated values\nThe script cannot be executed", #list)) return else print("the node is not animated") end local sceneGraph = octane.project.getSceneGraph() local interval = sceneGraph:getAnimationTimeSpan() local fps = octane.project.getProjectSettings():getAttribute(octane.A_FRAMES_PER_SECOND) local totalFrames = fps * (interval[2] - interval[1]) or 1 if totalFrames ~= 0 then showInfo("The scene seems already animated, unwanted results can occur") end -- choose a different way if the original Camera is a Panoramic or Thin Lens Camera node if tileMode == 1 then --PANORAMIC CAMERA-- v3 ONLY -- get the position of the selected node in the graph editor pos = {} pos = rt.position -- Create the new graph that collect everything params = {} params.type = octane.GT_STANDARD params.name = rt.name.."_CuneMap_6_faces" params.position = {pos[1], pos[2]+35} params.graphOwner = rt.graphOwner newGraph = octane.nodegraph.create(params) copy = newGraph:copyItemTree(rt) RT = newGraph:findFirstNode(octane.NT_RENDERTARGET) RT:configureEmptyPins() -- disable Post Effect local postOff = "" layer = octane.node.getInputNode(RT, octane.P_RENDER_PASSES) postP = octane.node.getInputNode(RT, octane.P_POST_PROCESSING) onOff = octane.node.getInputNode(postP, octane.P_ON_OFF) if onOff:getAttribute(octane.A_VALUE) == true then onOff:setAttribute(octane.A_VALUE, false) print ("Post processing has been disabled\n") postOff = "\nPost processing has been disabled\n" end RT.name = rt.name.."_CuneMap_6_faces" -- set the new resolution resCheck(RT) lockRes = octane.node.getAttribute(filmResT, octane.A_ASPECT_RATIO) if lockRes ~= 0 then octane.node.setAttribute(filmResT, octane.A_ASPECT_RATIO, 0) end res = octane.node.getAttribute(filmResT, octane.A_VALUE) newRes = {1536, 1536} octane.node.setAttribute(filmResT, octane.A_VALUE, newRes) res = octane.node.getAttribute(filmResT, octane.A_VALUE) octane.node.setAttribute(filmResT, octane.A_ASPECT_RATIO, 1) -- create the Lens shift animator camera = octane.node.getInputNode(RT, octane.P_CAMERA) projection = octane.node.getInputNode(camera, octane.P_CAMERA_MODE) stereo = octane.node.getInputNode(camera, octane.P_STEREO_OUTPUT) stereoV = stereo:getAttribute(octane.A_VALUE) stON = 2 if stereoV == 0 then stON = 1 end frm = 1/fps time = {} for i=1, 6*stON do time[i]= i*frm end period = frm*6*stON projectionA = {3, 4, 5, 6, 7, 8} if stON == 2 then projectionA = {3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8} newGraph.name = rt.name.."_CuneMap_12_faces" RT.name = rt.name.."_CuneMap_12_faces" end RTout = octane.node.create { name = RT.name.."_OUT", type = octane.NT_OUT_RENDERTARGET, graphOwner = newGraph, } RTout:connectTo(octane.P_INPUT, RT) stereoA = {0, 0, 0, 0, 0, 0} if stON == 2 then stereoA = {1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2} stereo:setAnimator(octane.A_VALUE, time, stereoA, period, true, 1, period) end projection:setAnimator(octane.A_VALUE, time, projectionA, period, true, 1, period) newGraph:unfold(true) -- show the final result showResults(string.format("Panoramic Cube Map animation created\nNumber of faces/frames = %d\n%s", 6*stON, postOff)) elseif tileMode == 2 then --THIN LENS CAMERA-- -- figure out if the autofocus is active or not, and show an error if yes auto = cam:getInputNode(octane.P_AUTOFOCUS) if auto ~= nil and auto:getAttribute(octane.A_VALUE) == true then showError("Please, disable Autofocus in the Thin Lens Camera node and run the script again") return end --GUI-- --TILES-- -- create the Tiles label local noteLbl1 = octane.gui.create { type = octane.gui.componentType.LABEL, text = "Number of Tiles per axis =", width = 170, } -- create a numeric field for the Tiles number local num1 = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, minValue = 2, name = "Tiles", width = 60, enable = true, value = 2, maxValue = 89 } -- for layouting all the elements of the Tiles we use a group local dimGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "Thin Lens Camera Tiles", y = 10, rows = 1, cols = 2, children = { noteLbl1, num1 }, padding = { 2 }, inset = { 5 }, } local textA = octane.gui.create { type = octane.gui.componentType.LABEL, text = "", width = 240, height = 20, } local textD = octane.gui.create { type = octane.gui.componentType.LABEL, text = "", width = 240, height = 20, } -- for layouting the text we use a group local textGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 2, cols = 1, children = { textA, textD, }, padding = { 2 }, inset = { 2 }, } -- BUTTONS -- local createButton = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Create", width = 100, height = 20, tooltip = "Create the Tiled Camera", } local exitButton = octane.gui.create { type = octane.gui.componentType.BUTTON, text = "Exit", width = 100, height = 20, tooltip = "Exit from the script", } local buttonGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 1, cols = 2, children = { exitButton, createButton}, padding = { 5 }, border = false, } -- group that layouts the other groups local layoutGrp = octane.gui.create { type = octane.gui.componentType.GROUP, text = "", rows = 3, cols = 1, children = { dimGrp, textGrp, buttonGrp, }, centre = true, padding = { 2 }, border = false, debug = false, -- true to show the outlines of the group, handy } -- window that holds all components local PlaneWindow = octane.gui.create { type = octane.gui.componentType.WINDOW, text = "Tiled Camera Ani v"..version, children = { layoutGrp }, width = layoutGrp:getProperties().width+14, height = layoutGrp:getProperties().height, } -- END GUI CODE -- -- enable all the ui num1 :updateProperties{ enable = true } createButton :updateProperties{ enable = false} exitButton :updateProperties{ enable = true } --button functions local function createGraph() PlaneWindow:closeWindow(true) end local function exitAndDontCreateGraph() PlaneWindow:closeWindow(false) end -- callback handling the GUI elements local function guiCallback(component, event) -- Tiles calc -- Tiles = num1.value resCheck(rt) res1 = octane.node.getAttribute(filmResT, octane.A_VALUE) nRes = {math.floor(res1[1]/Tiles), math.floor(res1[2]/Tiles)} textA.text = string.format("Original Resolution = %d x %d", res1[1], res1[2]) textD.text = string.format("%d tiles %d x %d - Merged = %d x %d", Tiles*Tiles, nRes[1], nRes[2], nRes[1]*Tiles, nRes[2]*Tiles) if nRes[1] <= 3 or nRes[2] <= 3 then textD.textColour = { 224, 0, 0, 255 } createButton:updateProperties{ enable = false } elseif res1[1] == nRes[1]*num1.value and res1[2] == nRes[2]*num1.value then textD.textColour = { 240, 240, 240, 255 } createButton:updateProperties{ enable = true } elseif res1[1] ~= nRes[1]*num1.value or res1[2] ~= nRes[2]*num1.value then textD.textColour = { 224, 128, 0, 255 } createButton:updateProperties{ enable = true } end exitButton:updateProperties{ enable = true } -- close -- if component == createButton then createGraph() print("Total Tiles =", Tiles*Tiles, "\n") elseif component == exitButton then exitAndDontCreateGraph() print ("Tiles not created") elseif component == PlaneWindow then --when the window closes, print something if event == octane.gui.eventType.WINDOW_CLOSE then print ("Happy rendering :-)\n") end end end -- hookup the callback with all the GUI elements num1 :updateProperties { callback = guiCallback } createButton :updateProperties { callback = guiCallback } exitButton :updateProperties { callback = guiCallback } PlaneWindow :updateProperties { callback = guiCallback } -- the script will block here until the window closes local create = PlaneWindow:showWindow() -- stop the script if the user clicked the exit button if not create then return end -- get the position of the selected node in the graph editor pos = {} pos = rt.position -- Create the Tiled graph that collect everything params = {} params.type = octane.GT_STANDARD params.name = rt.name.."_Tiled_"..Tiles*Tiles.."_frames" params.position = {pos[1], pos[2]+35} params.graphOwner = rt.graphOwner newGraph = octane.nodegraph.create(params) copy = newGraph:copyItemTree(rt) RT = newGraph:findFirstNode(octane.NT_RENDERTARGET) RT.name = rt.name.."_Tiled_"..Tiles*Tiles.."_frames" RTout = octane.node.create { name = RT.name.."_OUT", type = octane.NT_OUT_RENDERTARGET, graphOwner = newGraph, } RTout:connectTo(octane.P_INPUT, RT) -- disable Vignetting and Post Effect local vignOff = {false, ""} imager = octane.node.getInputNode(RT, octane.P_IMAGER) vignet = octane.node.getInputNode(imager, octane.P_VIGNETTING) if vignet:getAttribute(octane.A_VALUE)[1] > 0 then vignet:setAttribute(octane.A_VALUE, 0) print ("Vignetting has been disabled\n") vignOff[1] = true vignOff[2] = "*Vignetting has been disabled" end postP = octane.node.getInputNode(RT, octane.P_POST_PROCESSING) if postP ~= nil then onOff = octane.node.getInputNode(postP, octane.P_ON_OFF) layer = octane.node.getInputNode(RT, octane.P_RENDER_PASSES) end local postOff = {false, ""} if postP ~= nil and onOff:getAttribute(octane.A_VALUE) == true then RTpost = octane.node.create { name = rt.name.."_MergedPostProLayer", type = octane.NT_RENDERTARGET, graphOwner = newGraph, position = {pos[1], pos[2]+50} } info ={} inputNode = {} inputNodeG = {} for i=1, RT:getPinCount() do inputNode[i] = RT:getConnectedNodeIx(i) if inputNode[i] == nil then i = i+1 elseif inputNode[i].pinOwned == true then RTpost:copyFromIx(i, inputNode[i]) elseif inputNode[i].pinOwned == false then if RT:getPinInfoIx(i).name ~= "camera" and RT:getPinInfoIx(i).name ~= "filmSettings" and RT:getPinInfoIx(i).name ~= "resolution" and RT:getPinInfoIx(i).name ~= "postproc" and RT:getPinInfoIx(i).name ~= "kernel" then RTpost:connectToIx(i, inputNode[i]) elseif RT:getPinInfoIx(i).name == "camera" or RT:getPinInfoIx(i).name == "filmSettings" or RT:getPinInfoIx(i).name == "resolution" or RT:getPinInfoIx(i).name == "postproc" or RT:getPinInfoIx(i).name == "kernel" then if inputNode[i].isOutputLinker == false then RTpost:copyFromIx(i, inputNode[i]) elseif inputNode[i].isOutputLinker == true then inputNodeG[i] = inputNode[i]:getConnectedNode(octane.P_INPUT) RTpost:copyFromIx(i, inputNodeG[i]) end end end end RTPout = octane.node.create { name = RTpost.name.."_OUT", type = octane.NT_OUT_RENDERTARGET, graphOwner = newGraph, } RTPout:connectTo(octane.P_INPUT, RTpost) -- set the minimum sampling for Post processing RT kernelP = octane.node.getInputNode(RTpost, octane.P_KERNEL) maxSampP = octane.node.getInputNode(kernelP, octane.P_MAX_SAMPLES) maxSampP:setAttribute(octane.A_VALUE, 32) -- disable the tiled Post processing onOff:setAttribute(octane.A_VALUE, false) print ("\nPost processing has been disabled in the Tiled Render Target\nNew Post processing Render Target has been created\n") postOff[1] = true postOff[2] = "**Post processing has been disabled in the Tiled Render Target\n**New Post processing Render Target has been create" -- check if the Post Processing pass is active or not if layer == nil then RTpost:configureEmptyPins() Multi = octane.node.getInputNode(RTpost, octane.P_RENDER_PASSES) postL = octane.node.getInputNode(Multi, octane.P_RENDER_PASS_POST_PROCESSING) postL:setAttribute(octane.A_VALUE, true) else if layer ~= nil then layerP = octane.node.getInputNode(layer, octane.P_RENDER_PASS_POST_PROCESSING) layerP:setAttribute(octane.A_VALUE, false) RTpost:disconnect(octane.P_RENDER_PASSES) RTpost:configureEmptyPins() Multi = octane.node.getInputNode(RTpost, octane.P_RENDER_PASSES) postL = octane.node.getInputNode(Multi, octane.P_RENDER_PASS_POST_PROCESSING) postL:setAttribute(octane.A_VALUE, true) end end end -- set the new resolution resCheck(RT) res = octane.node.getAttribute(filmResT, octane.A_VALUE) print("Resolution =", res[1], "x", res[2]) newRes = {res[1]/Tiles, res[2]/Tiles} octane.node.setAttribute(filmResT, octane.A_VALUE, newRes) print("\n"..Tiles*Tiles, "tiles", math.floor(newRes[1]), "x", math.floor(newRes[2]), "\n") mergR = {math.floor(newRes[1])*Tiles, math.floor(newRes[2])*Tiles} print("Merged resolution =", mergR[1], "x", mergR[2]) -- set the new FOV camera = octane.node.getInputNode(RT, octane.P_CAMERA) fov = camera:getPinValue(octane.P_FOV) foc = camera:getPinValue(octane.P_FOCAL_LENGTH) print("\nFocal length = ", math.floor(foc), "\n") sense = camera:getPinValue(octane.P_SENSOR_WIDTH) zoom = math.deg(2 * math.atan(sense/2/foc/Tiles)) octane.node.setPinValue(camera, octane.P_FOV, zoom) foc = camera:getPinValue(octane.P_FOCAL_LENGTH) print("New focal length = ", math.floor(foc), "\n") -- create the Lens shift array shift = octane.node.getInputNode(camera, octane.P_LENS_SHIFT) shiftR = camera:getPinValue(octane.P_LENS_SHIFT) a = {} for i=1, Tiles do a[i] = (i)-0.5-(Tiles/2) end c={} for i=1, #a do for v=1, Tiles do table.insert(c, a[v]) end end b = {} for i=1, #c do b[i]=c[i] end table.sort(b) frm = 1/fps time = {} for i=1, #c do time[i]= i*frm end period = frm*#c Reverse(b) shiftA = {} for i=1, #c do shiftA[i] = {shiftR[1]+c[i], shiftR[2]+b[i]} end -- set the animator for the Lens shift value shift:setAnimator(octane.A_VALUE, time, shiftA, period, true, 1, period) -- make order newGraph:unfold() -- show the final result showResults(string.format("Number of tiles/frames = %d\nFinal merged Resolution = %dx%d\n\n%s\n\n%s", Tiles*Tiles, mergR[1], mergR[2], vignOff[2], postOff[2])) end