-- -- Description: -- This script renders all the available render targets of the loaded project as Cubemap or Equirectangular images to a user specified Directory created by this script. -- Additionally it writes a lua VR tour script suitable for the Orbx Media Player on android phones to the same directory, creating a package of files, -- the user specified folder of which can be dropped into the 'ORBX>media' folder on the phone so that it will appear in the media player when it starts. -- The script will also write stero pair versions of the image suitable for 3D display on a Leia Lume Pad 2 android tablet. -- A log file is created in the directory immediately above the package folder. -- -- Setup -- The Render Target to be rendered requires both a Camera and a Film settings with an Image resolution node be connected, otherwise ithe script will not complete. -- -- User supplied data. -- Asks the user for an output directory and saves all the images to that directory (_01.png, _02.png, ...) and optionally a tour script. -- Asks user for the file format of the output -- Requires recource files to be located in the Octane scripts directory in a sub folder called resources i.e. C:\Program Files\OTOY\Scripts\Recources, -- this can be changed by editing the script. -- -- -- @author Mark, Thomas -- @version 1.0 -- Last modified 6/23/2023 -- fixed issue exporting cubmaps -- -- @shortcut ctrl + v local version = "v1.0 - for OctaneRender 2022.1.1" function tprint (t, s) for k, v in pairs(t) do local kfmt = '["' .. tostring(k) ..'"]' if type(k) ~= 'string' then kfmt = '[' .. k .. ']' end local vfmt = '"'.. tostring(v) ..'"' if type(v) == 'table' then tprint(v, (s or '')..kfmt) else if type(v) ~= 'string' then vfmt = tostring(v) end --print(type(t)..(s or '')..kfmt..' = '..vfmt) print(" Table entry "..(s or '')..kfmt..' = '..vfmt) end end end -- Helper to pop-up an OkCancel dialog and return the button clicked function showWarning(text,msg) local ret = octane.gui.showDialog { type = octane.gui.dialogType.BUTTON_DIALOG, buttonTexts = {"Continue"}, icon = octane.gui.dialogIcon.WARNING, title = "WARNING:\n"..tostring(text), text = tostring(msg), } end -- Global table with settings local settings = { ["projectPath"] = octane.project.getCurrentProject(), ["renderTargets"] = octane.project.getSceneGraph():findNodes(octane.NT_RENDERTARGET, true), ["outputDirectory"] = octane.file.getParentDirectory(octane.project.getCurrentProject()), ["enabled"] = {}, ["fileType"] = {}, ["hotspotAngle"] = {}, ["audiofilePath"] = {}, } -- filter out render targets with no kernel local filtered = {} local strSkippedRenderTargets = "" for ix, renderTargetNode in ipairs(settings.renderTargets) do if renderTargetNode:getInputNode(octane.P_KERNEL) and renderTargetNode:getInputNode(octane.P_CAMERA) and renderTargetNode:getInputNode(octane. P_FILM_SETTINGS) ~= nil then table.insert(filtered, renderTargetNode) else strSkippedRenderTargets = strSkippedRenderTargets.. "\n" .. renderTargetNode.name end end if strSkippedRenderTargets ~= "" then strSkippedRenderTargets = strSkippedRenderTargets.."\n\n\nTo process these Render Targets, please exit the script and connect them to one or more of the following node types. \n\nKernel, Camera, Film settings." showWarning("The following Render Targets have missing Kernels, Cameras, or Film settings nodes and will not be processed. Film settings Nodes require an Image resolution Node.", strSkippedRenderTargets) end settings.renderTargets = filtered print("Selected Render Targets") tprint(settings.renderTargets) -- sorts a table alpha numerically -- (snippet from http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua) function alphanumsort(nodes) local function padnum(d) local dec, n = string.match(d, "(%.?)0*(.+)") return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n) end local function compare(n0, n1) local a, b = n0.name, n1.name return a:gsub("%.?%d+",padnum)..("%3d"):format(#b) < b:gsub("%.?%d+",padnum)..("%3d"):format(#a) end table.sort(nodes, compare) end -- sort the render targets alphabetically alphanumsort(settings.renderTargets) print("") print("RenderTargets sorted") -- If no render targets are found, error out if #settings.renderTargets == 0 then error("No Render Targets with the correct set of connected Nodes were found. They must hava a Camera, a Film settings with Image resolution Node and a Kernal Node attatched. Exiting...") end -------------------------------------------------------------------------------------------------- -- Gui helpers print("Building GUI") -- Creates a text label and returns it function createLabel(text, width, height, tooltip) 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 tooltip = tooltip, } 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 x = x, y = y, } end -- Create a group and return 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 -- Recursively enable or disable all components in a window function setEnabled(component, enable) if component.type == octane.gui.componentType.WINDOW or component.type == octane.gui.componentType.GROUP then for _, childComponent in ipairs(component.children) do setEnabled(childComponent, enable) end else component.enable = enable end end -- GUI layout constants local BUTTON_WIDTH = 100 local BUTTON_HEIGHT = 20 local WIDE_LBL_WIDTH = 226 local NARROW_LBL_WIDTH = BUTTON_WIDTH local LBL_HEIGHT = 20 local GRP_PAD = 2 local tableChildren = {} local HotSpotAngle = 25 local AudioFilePath = "" -- Build dynamic rendertarget list component of GUI ----------------------------------------------------------------------------------------- for ix, renderTarget in ipairs(settings.renderTargets) do -- Check if a kernel is connected to the render target local kernelNode = renderTarget:getInputNode(octane.P_KERNEL) local strDescription = string.format("%d: %s", ix, renderTarget.name.. " - " ..kernelNode:getPinValue(octane.P_MAX_SAMPLES) .. " spp") -- GUI Controls local lblRenderTarget = createLabel(strDescription, WIDE_LBL_WIDTH - 50, LBL_HEIGHT) local enableBox = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "render", enable = true, width = 60, callback = function(me) settings.enabled[ix] = not settings.enabled[ix] print(ix, settings.enabled[ix]) end } local lblHotSpotAngle = createLabel("Hot Spot Angle", NARROW_LBL_WIDTH - 20, LBL_HEIGHT) local sldHotSpotLocation = octane.gui.create { type = octane.gui.componentType.SLIDER, -- type of the component name = "Hot Spot Angle", -- name of the component value = 25, -- value of the slider minValue = 0, -- minimum value of the slider maxValue = 360, -- maximum value of the slider step = 5, -- interval between 2 discrete slider values width = WIDE_LBL_WIDTH, -- width of the slider in pixels height = LBL_HEIGHT , -- height of the slider in pixels enable = true, tooltip = "Angle from left of panorama center to hotspot location.", callback = function(me,VALUE_CHANGE) print(me.value) settings.hotspotAngle[ix] = me.value print(ix, settings.hotspotAngle[ix]) end } local txtAudioDirectory = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "", width = WIDE_LBL_WIDTH , height = LBL_HEIGHT, enable = true, tooltip = "Path to .wav file that will play when viewing this panorama", callback = function(me,VALUE_CHANGE) print("-") print("Entered txtAudioDirectory callback") print(me.text) settings.audiofilePath[ix] = me.text print(ix, settings.audiofilePath[ix]) print("Exited txtAudioDirectory callback") end } local btnBrowse = octane.gui.create { type = octane.gui.componentType.BUTTON, -- type of the component name = "Audio File", -- name of the component text = "Audio File...", -- button text width = BUTTON_WIDTH - 20, -- width in pixels height = BUTTON_HEIGHT, -- height in pixels tooltip = "Specify the folder containing a .wav file to play while viewing the panorama.", -- tooltip text x = 0, y = 0, callback = function(me) -- Ask the user for an output directory for the results print("-") print("Entered btnBrowse callback") local ret = octane.gui.showDialog { type = octane.gui.dialogType.FILE_DIALOG, title = "Select a .wav audio file", path = octane.file.getParentDirectory(settings.projectPath), browseDirectory = false, save = false, wildcards = "*.wav" } if not ret.result or ret.result == "" then txtAudioDirectory.text = "" else --settings.audiofilePath[ix] = ret.result txtAudioDirectory.text = octane.file.getFileName(ret.result) txtAudioDirectory.tooltip = octane.file.getFileName(ret.result) end for _,child in ipairs(tableChildren) do if child.type == octane.gui.componentType.TEXT_EDITOR then settings.audiofilePath[ix] = octane.file.getFileName(ret.result) print(" ".. ix, settings.audiofilePath[ix]) end end print("Exited btnBrowse callback") print("-") end } table.insert(tableChildren , lblRenderTarget) table.insert(tableChildren , enableBox) table.insert(tableChildren , createLabel("", 5, 1)) -- Padding table.insert(tableChildren , lblHotSpotAngle) table.insert(tableChildren , sldHotSpotLocation) table.insert(tableChildren , createLabel("", 5, 1)) -- Padding table.insert(tableChildren , btnBrowse) table.insert(tableChildren , txtAudioDirectory) table.insert(settings.enabled , true) table.insert(settings.fileType , octane.imageSaveType.PNG8) table.insert(settings.hotspotAngle, sldHotSpotLocation.value) table.insert(settings.audiofilePath, txtAudioDirectory.text) end -- RenderTarget list -- Add a disable all button to render target list section of GUI local disableAllButton = octane.gui.create { type = octane.gui.componentType.BUTTON, width = BUTTON_WIDTH - 15, height = BUTTON_HEIGHT, text = "Deselect All", x = 12, y = 5, callback = function(me) local enable = nil if me.text == "Deselect All" then me.text = "Select All" enable = false else me.text = "Deselect All" enable = true end for ix = 1, #settings.renderTargets do settings.enabled[ix] = enable end for _,child in ipairs(tableChildren) do if child.type == octane.gui.componentType.CHECK_BOX then child.checked = enable end end end } -- Add an apply all button to render target list section of GUI local applyAllButton = octane.gui.create { type = octane.gui.componentType.BUTTON, width = BUTTON_WIDTH - 20, height = BUTTON_HEIGHT, text = "Apply to all", x = 0, y = 5, tooltip = "Applies the first .wav file to all subsequent panoramas.", callback = function(me) print("Entered applyAllButton callback") --print(" ") --local blnEnabled = nil --for ix = 1, #settings.renderTargets do --settings.enabled[ix] = nil --end --for ix = 1, #settings.renderTargets do --settings.enabled[ix] = true --end local numCount = 0 -- "First entyry in table settings.audiofilePath was set by browse button, others remain empty" for _, child in ipairs(tableChildren) do if child.type == octane.gui.componentType.TEXT_EDITOR then numCount = numCount+1 --child.text = settings.audiofilePath[1] print(" initial textbox text @ index ".. numCount, child.text) print(" settings table entry 1 = ", settings.audiofilePath[1]) child.text = settings.audiofilePath[1] print(" updated textbox text @ index ".. numCount, child.text) settings.audiofilePath[numCount] = settings.audiofilePath[1] print(" updated settings table entry ".. numCount, settings.audiofilePath[numcount]) end end --for ix = 1, #settings.renderTargets do --settings.enabled[ix] = true --end print("-") print("List of Settings @ applyAllButton Callback.") tprint(settings) print("-") print("Exited applyAllButton callback") print("-") end } -- Add an clear all button to render target list section of GUI local clearAllButton = octane.gui.create { type = octane.gui.componentType.BUTTON, width = BUTTON_WIDTH - 20, height = BUTTON_HEIGHT, text = "Clear all", x = 0, y = 5, tooltip = "Clears all .wav files already associated with panoramas.", callback = function(me) local enable = nil for _,child in ipairs(tableChildren) do if child.type == octane.gui.componentType.TEXT_EDITOR then child.text = "" settings.audiofilePath = "" end end end } -- Add controls to table of controls table.insert(tableChildren, 1, disableAllButton) table.insert(tableChildren, 2, createLabel("", 0, 1)) table.insert(tableChildren, 3, createLabel("", 0, 1)) table.insert(tableChildren, 4, createLabel("", 0, 1)) table.insert(tableChildren, 5, createLabel("", 0, 1)) table.insert(tableChildren, 6, createLabel("", 0, 1)) table.insert(tableChildren, 7, applyAllButton) table.insert(tableChildren, 8, clearAllButton) -- Build remaining GUI --------------------------------------------------------------------------------------------------------------- createButton("output", "Output Folder...", BUTTON_WIDTH, BUTTON_HEIGHT, "Specify the directory to receive the rendered output files.") local renderTargetsGrp = octane.gui.create { type = octane.gui.componentType.GROUP, children = tableChildren, border = true, cols = 8, rows = #tableChildren / 8, text = "Available Render Targets:", padding = { GRP_PAD + 1 }, } local lblTourTitle = createLabel("Tour Title", NARROW_LBL_WIDTH - 150, LBL_HEIGHT) local txtEditorTourTitle = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "VR Tour Package", x = 0 , -- calculate the width to make the 2 stacked groups the same size -- no magic formula just trial and error ;) width = NARROW_LBL_WIDTH + 75, height = 20, enable = false, tooltip = "Title of folder to contain all VR Tour related files. The folder will be created in the specified output directory. To play its contents the folder should be placed in the OMP 'Media' directory on the android phone.", } local lblRescourceLocation = createLabel("Rescources Location", WIDE_LBL_WIDTH - 50, LBL_HEIGHT) local txtRescourceLocation = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = "Rescources Location", x = 0 , -- calculate the width to make the 2 stacked groups the same size -- no magic formula just trial and error ;) width = WIDE_LBL_WIDTH, height = 20, enable = false, tooltip = "Location of Hotspot image files.", } -- Checkbox for output - Orbx Media Package local chkCreatePackage = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Create OMP Package", enable = true, width = 190, checked = true, tooltip = "Creates a Package of files including images for Otoy's Orbx Media Player suitable for playback on Samsung's Gear VR.", } -- Checkbox for output - Images only local chkCreateImagesOnly = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Create Images Only", enable = true, width = 190, checked = false, tooltip = "Limits output to just those Images that can be produced from the selected Render Targets.", } -- Checkbox for output - Orbx Media Package local chkRetainTemporaryNodes = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Retain Temporary Nodes", enable = true, width = 170, checked = false, tooltip = "Retain all the temporary Camer, Film Setting and Image Resolution Nodes generated vy the script.", } -- maximum time numericbox local lblMaxTime = createLabel("Maximum Time (in minutes)", NARROW_LBL_WIDTH - 0, LBL_HEIGHT) local boxMaxTime = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, width = NARROW_LBL_WIDTH, height = LBL_HEIGHT, maxValue = 1440, minValue = 0, step = .5, value = 0, } -- Maximum samples numericbox local lblMaxSamples = createLabel("Maximum Samples/Pixel", WIDE_LBL_WIDTH-50, LBL_HEIGHT) local boxMaxSamples = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, width = NARROW_LBL_WIDTH, height = LBL_HEIGHT, maxValue = 256000, minValue = 0, step = 50, value = 0, } -- Camera Focal Length numericbox local lblFocalLength = createLabel("Focal Length (mm)", WIDE_LBL_WIDTH-50, LBL_HEIGHT) local boxFocalLength = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, width = NARROW_LBL_WIDTH, height = LBL_HEIGHT, maxValue = 60, minValue = 40, step = 1, value = 55, tooltip = "Larger values make objects closer.", } -- IPD numericbox, units = meters local lblIPD = createLabel("Interpupillary Distance", WIDE_LBL_WIDTH-50, LBL_HEIGHT) local boxIPD = octane.gui.create { type = octane.gui.componentType.NUMERIC_BOX, width = NARROW_LBL_WIDTH, height = LBL_HEIGHT, maxValue = 0.095, minValue = 0.035, step = .001, value = .063, tooltip = "Larger values make objects appear smaller.", } local txtResolution = "" local lblResolution = createLabel("Image Resolution (2048 x 1024)", WIDE_LBL_WIDTH-50, LBL_HEIGHT) local cboResolution = octane.gui.create { type = octane.gui.componentType.COMBO_BOX, items = { "Side-by-Side", "Cubemap", "High", "Medium", "Low", "Draft" }, selectedIx = 4, width = 100, tooltip = "Side-by-Side = 5160 x 1600, Cubemap = 80192 x 1536, High = 4096 x 2048, Medium = 3840 x 1920, Low = 2048 x 1024, Draft = 1024 x 512" } -- Checkboxes for image output type local chkEquirectangularImage = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Mono Equirectangular Image", enable = true, width = 190, checked = true, tooltip = "Available Resolutions = 4096 x 2048 / 3840 x 1920 / 2048 x 1024 / 1024 x 512", } local chkCubemapImage = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Stereo Cubemap Image", enable = true, width = 180, checked = false, tooltip = "Available Resolution = 18432 x 1536", } local chkSBSImage = octane.gui.create { type = octane.gui.componentType.CHECK_BOX, text = "Lume Pad 2 Side-by-Side Image", enable = true, width = 200, checked = false, tooltip = "Available Resolution = 5160 x 1600", } local strHelp = [[Description: This script renders all the available render targets of the loaded project as Cubemap or Equirectangular images to a user specified Directory created by the script. Additionally it writes a Lua VR tour script suitable for the Orbx Media Player on Android phones to the same directory, creating a package of files in the the user specified directory which can be dropped into the 'ORBX>media' folder on the phone so that it will appear in the media player when it starts. The script will also write stero pair versions of the image suitable for 3D display on a Leia Lume Pad 2 android tablet. A log file is created in the directory immediately above the package folder. Setup: The Render Target to be rendered requires both a Camera Node and a Film settings Node with an Image resolution Node be connected to it, otherwise ithe script will not complete. User Supplied Data: Asks the user for an output directory and saves all the images to that directory (_01.png, _02.png, ...) and optionally a tour script. Asks user for the file format of the output. Requires recource files to be located in the Octane scripts directory in a sub folder called resources i.e. C:\Program Files\OTOY\Scripts\Recources, this can be changed by editing the script.]] local lblHelp = createLabel("...", NARROW_LBL_WIDTH - 50, LBL_HEIGHT, strHelp) local lblSpacer = createLabel("", NARROW_LBL_WIDTH - 50, LBL_HEIGHT) local lblRenderSettings = createLabel("Render Settings:", NARROW_LBL_WIDTH +30, LBL_HEIGHT) local lblCameraSettings = createLabel("Camera Settings:", NARROW_LBL_WIDTH +30, LBL_HEIGHT) local lblImageFormatSettings = createLabel("Image Format Settings:", NARROW_LBL_WIDTH +30, LBL_HEIGHT) local lblResolutionSettings = createLabel("Image Resolution Settings:", NARROW_LBL_WIDTH +30, LBL_HEIGHT) -- Group GUI compoments grpSettings1 = createGroup("grpEsettings1", { lblTourTitle, txtEditorTourTitle, chkCreatePackage, chkCreateImagesOnly, chkRetainTemporaryNodes , lblRenderSettings, lblMaxTime, boxMaxTime,lblMaxSamples,boxMaxSamples , lblCameraSettings,lblFocalLength, boxFocalLength, lblIPD, boxIPD , lblImageFormatSettings, chkEquirectangularImage, chkCubemapImage, chkSBSImage , lblSpacer , lblResolutionSettings, lblResolution, cboResolution, lblSpacer , lblHelp , }, false, 5, 5, nil, nil, "Settings1", { 3 }, nil, false) -- Output location local btnOutput = createButton("output", "Output Directory...", BUTTON_WIDTH + 10, BUTTON_HEIGHT, "Specify the directory in which the folder containing all the necessary files will be created. The folder will be named per the Tour Title above.") -- Create an editor that will show the chosen file path local txtEditorDirectory = octane.gui.create { type = octane.gui.componentType.TEXT_EDITOR, text = octane.file.getParentDirectory(octane.project.getCurrentProject()), x = 0 , -- calculate the width to make the 2 stacked groups the same size -- no magic formula just trial and error ;) width = renderTargetsGrp.width - btnOutput.width - 30, height = 20 , enable = false , } grpSettings2 = createGroup("grpEsettings1", { btnOutput, txtEditorDirectory}, false, 1, 2, nil, nil, "Settings2", { 5 }, nil, false) local settingsGrp = octane.gui.create { type = octane.gui.componentType.GROUP, children = { grpSettings1, grpSettings2, }, text = "Settings: (nonzero values overide all kernels)", border = true, cols = 1, rows = 2, width = renderTargetsGrp.width, padding = {0}, } local progressBar = octane.gui.create { type = octane.gui.componentType.PROGRESS_BAR, width = settingsGrp.width - 8, height = LBL_HEIGHT, } -- Buttons (ok not used in this case) local btnApply = createButton("apply", "Apply", BUTTON_WIDTH, BUTTON_HEIGHT, "Start processing all selected render targets. if no output directory is specified, a dry run is performed") local btnCancel = createButton("cancel", "Cancel", BUTTON_WIDTH, BUTTON_HEIGHT, "Cancel.") grpExecute = createGroup("grpExecute", { btnApply, btnCancel}, false, 1, 2, nil, nil, "Execute", { 5 }, nil, false) -- this group holds everything together local layoutGrp = octane.gui.create { type = octane.gui.componentType.GROUP, rows = 4, cols = 1, children = {renderTargetsGrp, settingsGrp, progressBar, grpExecute}, border = false, padding = { 2 }, centre = true, } -- Window that holds all components local wndMain = octane.gui.create { type = octane.gui.componentType.WINDOW, text = "Create & package an OTOY ORBX Media Player VR Tour.", children = { layoutGrp }, width = layoutGrp.width, height = layoutGrp.height, } print("GUI Built") print("-") rv = octane.project.getCurrentProject() print("Project File Path:", rv) print(" ") print("Setting Callbacks") -- Callback function for the GUI Controls local function guiCallback(component, event) print(" ") print("Entering GUI Callback") if component == chkCreateImagesOnly then print(" @ Callback - chkCreateImagesOnly") if chkCreateImagesOnly == true then --print(" @ chkCreateImagesOnly - chkCreateImagesOnly.checked == true") --chkCreatePackage.enable = true --chkCreatePackage.checked = false --chkCreateImagesOnly.enabled = false else print(" @ chkCreateImagesOnly - chkCreateImagesOnly.checked == false") chkCreatePackage.enable = true chkCreatePackage.checked = false chkCreateImagesOnly.enable = false end end if component == chkCreatePackage then print(" @ Callback - chkCreatePackage") if chkCreatePackage == true then --print(" @ chkCreatePackage - chkCreatePackage.checked == true") --chkCreateImagesOnly.enable = true --chkCreateImagesOnly.checked = false --chkCreatePackage.enable = false else print(" @ chkCreatePackage - chkCreatePackage.checked == false") chkCreateImagesOnly.enable = true chkCreateImagesOnly.checked = false chkCreatePackage.enable = false end end if component == btnOutput then print(" @ Callback - btnOutput") -- 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 settings.outputDirectory = octane.file.getParentDirectory(octane.project.getCurrentProject()) else settings.outputDirectory = ret.result txtEditorDirectory.text = settings.outputDirectory end elseif component == cboResolution then print(" @ Callback - cboResolution") if cboResolution.selectedIx == 1 then if chkEquirectangularImage.checked == true then cboResolution.selectedIx = 3 txtResolution = "4096, 2048" lblResolution.text = "Image Resolution (4096 x 2048)" else txtResolution = "5160, 1600" lblResolution.text = "Image Resolution (5160 x 1600)" end elseif cboResolution.selectedIx == 2 then if chkEquirectangularImage.checked == true then cboResolution.selectedIx = 3 txtResolution = "4096, 2048" lblResolution.text = "Image Resolution (4096 x 2048)" else txtResolution = "8192, 1536" lblResolution.text = "Image Resolution (8192 x 1536)" end elseif cboResolution.selectedIx == 3 then txtResolution = "4096, 2048" lblResolution.text = "Image Resolution (4096 x 2048)" elseif cboResolution.selectedIx == 4 then txtResolution = "3840, 1920" lblResolution.text = "Image Resolution (3840 x 1920)" elseif cboResolution.selectedIx == 5 then txtResolution = "2048, 1024" lblResolution.text = "Image Resolution (2048 x 1024)" elseif cboResolution.selectedIx == 6 then txtResolution = "1024, 512" lblResolution.text = "Image Resolution (1024 x 512)" end print(" Selected Resolution" .. " " .. txtResolution) elseif component == chkCubemapImage then print(" @ Callback - CubemapImage") if chkCubemapImage.checked == true then print(" @ CubemapImage - chkCubemapImage.checked == true") chkEquirectangularImage.enable = true chkEquirectangularImage.checked = false chkSBSImage.enable = true chkSBSImage.checked = false cboResolution.selectedIx = 2 cboResolution.enable = false chkCubemapImage.enable = false end elseif component == chkEquirectangularImage then print(" @ Callback - EquirectangularImage") if chkEquirectangularImage.checked == true then print(" @ EquirectangularImage - chkEquirectangularImage.checked == true") chkCubemapImage.enable = true chkCubemapImage.checked = false chkSBSImage.enable = true chkSBSImage.checked = false cboResolution.selectedIx = 4 cboResolution.enable = true chkEquirectangularImage.enable = false end elseif component == chkSBSImage then print(" @ Callback - SBSImage") if chkSBSImage.checked == true then print(" @ SBSImage - chkSBSImage.checked == true") chkEquirectangularImage.enable = true chkEquirectangularImage.checked = false chkCubemapImage.enable = true chkCubemapImage.checked = false cboResolution.selectedIx = 1 cboResolution.enable = true chkSBSImage.enable = false end elseif component == btnApply then print(" @ btnApply") IS_CANCELLED = false btnCancel.enable = true print(" Calling batch rendering routine") batchRender() progressBar.text = "finished" elseif component == btnCancel then print(" @ btnCancel") cancelRender() wndMain:closeWindow() elseif component == wndMain then print(" @ wndMain") cancelRender() end print("Exiting GUI Callback") print("") end -- GUICallback print("Callbacks Set") print("-") print("Hooking up controls to Callbacks") -- Hookup GUI controls to their Callback function chkCreateImagesOnly.callback = guiCallback chkCreatePackage.callback = guiCallback boxMaxSamples.callback = guiCallback btnOutput.callback = guiCallback cboResolution.callback = guiCallback btnApply.callback = guiCallback btnCancel.callback = guiCallback chkCubemapImage.callback = guiCallback chkEquirectangularImage.callback = guiCallback chkSBSImage.callback = guiCallback print("Hookup complete") function cancelRender() IS_CANCELLED = true octane.render.callbackStop() end print("-------------------------------------------------------------------------------------------------------") print(" ") print("List of Settings prior to any user interaction.") tprint(settings) print(" ") -- String to hold VRTour.lua script strVRTour = "-- Camera Focal Length (".. boxFocalLength.value .. ")\n" .. "-- Camera IPD (".. boxIPD.value .. ")\n" .. "-- ".. lblResolution.text .. "\n" .. "-- link values connect to next title value to display" .. "\n" .. "-- url values correspond to image file names" .. "\n\n" .. "return {" .. "\n\t" .. "scenes = {" ------------------------------------------------------------------------------------------------------------------------------------ -- MAIN ROUTINE ------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------ function batchRender() print("-") print(" List of Settings after user interaction and prior to executing main routine.") tprint(settings) print(" ") -- Calculate the progress bar step numCount = 0 local numEnabled = 0 local path = nil local ms = boxMaxSamples.value local KernelType = "" local strLog = "" progressBar.progress = 0 print("Creating Tour Location path") -- Create a directory for all files based on tour title. path = string.format("%s/%s", settings.outputDirectory, txtEditorTourTitle.text ) octane.file.createDirectory(path) print("Tour Location path created: path = ", path) print("-") print("Beginning batch rendering, available Render Target count = ", #settings.renderTargets) print("") local tblSelectedRenderTargets ={} local numImagesSaved print("Building Tables of user selected Render Targets") for ix, renderTarget in ipairs(settings.renderTargets) do if settings.enabled[ix] == true then print(" Adding Render Target to Selected RenderTargets table", ix) table.insert(tblSelectedRenderTargets, settings.renderTargets[ix]) end end print("Selected Render Target count = ", #tblSelectedRenderTargets) print("") print("Table build complete") print("Table of selected RenderTargets:") tprint(tblSelectedRenderTargets) print (" ") numEnabled = #tblSelectedRenderTargets local progressBarStep = 1 / numEnabled local flgMessage = false if boxMaxSamples.value ~= 0 then flgMessage = true end if boxMaxTime.value == 0 then mrt = 86400.000000 -- Octane default of 24 hours else flgMessage = true end if flgMessage == true then showWarning("You have overidden every Render Target's 'Default Time' and/or 'Maximum Samples' setting.","Each rendering will continue until either the overide samples/pixel value is reached or the overide maximum render time is exceeded before proceeding to the next.") end print ("Begin batch image export") print("-----------------------------------------------------------") -- Render all the render targets that are enabled local indx = nil for ix, renderTarget in ipairs(tblSelectedRenderTargets) do -- print" " print("Is Canceled = ", IS_CANCELLED) print" " -- Break out if we're cancelled and set it in the progress bar if IS_CANCELLED then print("Batch render cancelled") progressBar.text = "cancelled" break end print("Exporting image #", ix, "of", #tblSelectedRenderTargets, "Images" ) -- Only render if the rt is enabled print(" ") print("Checking for enabled Render Targets.") tprint(settings.enabled) print(" ") print("Index of Render Target being exported = ", ix) -- Check if a kernel is connected to the render target local kernelNode = renderTarget:getInputNode(octane.P_KERNEL) local kernelNodeMaxSamples = kernelNode:getPinValue(octane.P_MAX_SAMPLES) print(" ") print("Aquiring Kernel Properties.") -- Figure out the type of kernel, set maximim samples (you can use .type here) if kernelNode ~= nil then if kernelNode.type == octane.NT_KERN_DIRECTLIGHTING or kernelNode.type == octane.NT_KERN_PATHTRACING or kernelNode.type == octane.NT_KERN_PMC or kernelNode.type == octane.NT_KERN_INFO or kernelNode.type == octane.NT_KERN_MATPREVIEW then --print(string.format("kernel with %d samples/px", kernelNode:getPinValue(octane.P_MAX_SAMPLES))) if boxMaxSamples.value == 0 then -- do nothing else kernelNode:setPinValue(octane.P_MAX_SAMPLES, boxMaxSamples.value, true) end if boxMaxTime.value == 0 then mrt = 86400.000000 -- Octane default of 24 hours else mrt = boxMaxTime.value * 60 -- Turn minutes into seconds end if kernelNode:getProperties().type == octane.NT_KERN_DIRECTLIGHTING then KernelType = "Direct Lighting Kernel" end if kernelNode:getProperties().type == octane.NT_KERN_PATHTRACING then KernelType = "Path Tracing Kernel" end if kernelNode:getProperties().type == octane.NT_KERN_PMC then KernelType = "PMC Kernel" end if kernelNode:getProperties().type == octane.NT_KERN_INFO then KernelType = "Info Kernel" end if kernelNode:getProperties().type == octane.NT_KERN_MATPREVIEW then KernelType = "Material Preview Kernel" end else print("unknown kernel", kernelNode.type) end else print("render target has no connected kernel node") end print("Properties aquired.") -- Build string of tour lua script -- Gather variables from settings table print("-") print("Retrieving variables from Settings Table for Render Target with index of:", ix) print("Settings Table:") tprint(settings) print(" ") print("Assigning settings to variables") local angleHotSpot = settings.hotspotAngle[ix] local audioFile = settings.audiofilePath[ix] local titleImage = renderTarget.name local urlName = titleImage ..".png" local linkName = "" print("Assignment complete") print("-") print("Composing Virtual Tour Script entry for image number ", ix) print(" ", "Audio file to use: ", settings.audiofilePath[ix]) -- Derive linkName numCount = numCount + 1 if numCount < #tblSelectedRenderTargets then linkName = settings.renderTargets[numCount + 1].name print(" ", ix, renderTarget.name, "Linked to: ", linkName) else linkName = settings.renderTargets[1].name print(" ", ix, renderTarget.name, "Linked to: ", linkName) end -- Write lua code strings strVRTour = strVRTour .. string.format( "\n\t\t\t\t".. "{ -- Scene " .. ix .. "\n\t\t\t\t\t" .. "title = ".. [["]] .. titleImage .. [["]] .. "," .. "\n\t\t\t\t\t" .. "url = " .. [["]] .. urlName .. [["]] .. "," .. "\n\t\t\t\t\t" .. "audio = " .. [["]] .. audioFile .. [["]] .. "," .. "\n\t\t\t\t\t" .. "onRollover = {") strVRTour = strVRTour .. string.format( "\n\t\t\t\t\t\t\t\t\t" .. "[" .. angleHotSpot .. "] = {" .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "link = " .. [["]] .. linkName .. [["]] .. "," .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "image = " .. [["next_arrowglow.png" ,]] .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "fov = 10," .. "\n\t\t\t\t\t\t\t\t\t\t\t" .. " },") -- Add hotspot to Exit if numCount >= #tblSelectedRenderTargets then strVRTour = strVRTour .. string.format( "\n\n\t\t\t\t\t\t\t\t\t" .. "[" .. angleHotSpot + 45 .. "] = {" .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "link = " .. [["exit_to_home"]] .. "," .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "image = " .. [["end_arrowglow.png" ,]] .. "\n\t\t\t\t\t\t\t\t\t\t\t\t" .. "fov = 10," .. "\n\t\t\t\t\t\t\t\t\t\t\t" .. " },") print(" ", ix, renderTarget.name, "Linked to: exit_to_home") end strVRTour = strVRTour .. string.format( "\n\t\t\t\t\t\t\t\t" .. " }," .. "\n\t\t\t\t" .. "}," .. "\n\n") print("") print("Composition Complete.") print(" ") -- Update the progress progressBar.text = string.format("rendering %s (%s)", renderTarget.name..", linked to ", path or "dry-run") -- Store the start time local ImageStartTime = os.clock() -- Render the image ------------------------------------------------------------------------------------------------------------ -- Copy the graph to prevent modification of the original scene. -- Create a full copy of the project so that we don't modify the original project print("Copying Scene Graph") local sceneCopy = octane.nodegraph.createRootGraph("Project Copy") local nodeCopies = sceneCopy:copyFrom(octane.project.getSceneGraph():getOwnedItems()) print("Copied nodes:") tprint(nodeCopies) print(" ") local tblCopiedRenderTargets = {} local tblCopiedCameras = {} print("Building table of enabled Rendertarget Nodes") for i, item in ipairs(nodeCopies) do if item:getProperties().type == octane.NT_RENDERTARGET then table.insert(tblCopiedRenderTargets, item) end end print("Table build complete.") -- Sort so order of indices match list of originals print(" ") print("Sorting table of copied Render Targets") alphanumsort(tblCopiedRenderTargets) print("Sort complete") print("Sorted table of copied Render Targets:") tprint(tblCopiedRenderTargets) -- Strip out all non rendertarget nodes from the table of nodes in the copied nodegraph -- since the table is not used again, do we need this? --for i, item in ipairs(nodeCopies) do --if item:getProperties().type ~= octane.NT_RENDERTARGET then --table.remove(nodeCopies, i) --end --end print(" ") print("Copy, then setup new Camera.") -- Check if a suitable camera is connected to the render target -- local cameraCopy = renderTarget:getConnectedNode(octane.P_CAMERA) -- print(cameraCopy.name) -- Get nodes connected to the render target to restore later local cameraOriginal = renderTarget:getConnectedNode(octane.P_CAMERA) local filmsettingsOriginal = renderTarget:getConnectedNode(octane.P_FILM_SETTINGS) local imageresolutionOriginal = filmsettingsOriginal:getConnectedNode(octane.P_RESOLUTION) -- Store Interpupillary Distance local strIPD = boxIPD.value -- Get location of Render Target on Node Graph local tblRenderTargetItems = renderTarget:getProperties() print(" ") print("Render Target Items") tprint(tblRenderTargetItems) print("Render Target position") print(tblRenderTargetItems["position"][1]) print(tblRenderTargetItems["position"][2]) print("Assign Render Target positions to variables") posGraphX = tblRenderTargetItems["position"][1] posGraphY = tblRenderTargetItems["position"][2] print(" ") -- Create a New Film Settings local nImageResolution_Temp = octane.node.create{type = octane.NT_IMAGE_RESOLUTION , name = "Temporary Image resolution", position = { posGraphX + 50, posGraphY + 50 }} local nFilmSettings_Temp = octane.node.create{type = octane.NT_FILM_SETTINGS, name = "Temporary Film settings", position = { posGraphX + 100, posGraphY + 100 }} -- Connect new Film Settings to original RenderTarget octane.node.connectToIx(renderTarget, 5, nFilmSettings_Temp, true) -- Connect new Image Resolution to New Film Settings octane.node.connectToIx(nFilmSettings_Temp, 1, nImageResolution_Temp, true) if chkEquirectangularImage.checked then -- Create a New Equirectangular Panoramic Camera nCamera_Temp = octane.node.create{type = octane.NT_CAM_PANORAMIC, name = "Temporary Panoramic Equirectangular Camera", position = { posGraphX + 150, posGraphY + 150 }} nCamera_Temp:setPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION), false) nCamera_Temp:setPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET), false) nCamera_Temp:setPinValue(octane.P_FOCAL_LENGTH, boxFocalLength.value, true) nCamera_Temp:setPinValue(octane.P_STEREO_OUTPUT, 0, true) nCamera_Temp:setPinValue(octane.P_CAMERA_MODE, 1, true) nCamera_Temp:setPinValue(octane.P_STEREO_DIST , strIPD, true) -- Connect new Camera to original RenderTarget octane.node.connectToIx(renderTarget, 1, nCamera_Temp, true) print("") print("Equirectangular Camera Settings:") print(nCamera_Temp.name .." Focal Length = ".. nCamera_Temp:getPinValue(octane.P_FOCAL_LENGTH)) --print(nCamera_Temp.name .." FOV = ".. nCamera_Temp:getPinValue(octane.P_FOV)) print(nCamera_Temp.name .." Stereo Output = ".. nCamera_Temp:getPinValue(octane.P_STEREO_OUTPUT)) print(nCamera_Temp.name .." Camera Mode = ".. nCamera_Temp:getPinValue(octane.P_CAMERA_MODE)) print(nCamera_Temp.name .." IPD = ".. nCamera_Temp:getPinValue(octane.P_STEREO_DIST )) print(nCamera_Temp.name .." Position xyz: ") tprint(nCamera_Temp:getPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION))) print(nCamera_Temp.name .." Target xyz: ") tprint(nCamera_Temp:getPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET))) print ("-") -- Create Image Resolution table local tblResolution = {} if cboResolution.selectedIx == 1 then table.insert(tblResolution, {5160, 1600}) end -- Lume Pad 2 if cboResolution.selectedIx == 2 then table.insert(tblResolution, {80192, 1536}) end -- Cubemap if cboResolution.selectedIx == 3 then table.insert(tblResolution, {4096, 2048}) end -- High if cboResolution.selectedIx == 4 then table.insert(tblResolution, {3840, 1920}) end -- Medium if cboResolution.selectedIx == 5 then table.insert(tblResolution, {2048, 1024}) end -- Low if cboResolution.selectedIx == 6 then table.insert(tblResolution, {1024, 512}) end -- Draft print("Selected Resolution to apply to Node:", nFilmSettings_Temp.name) tprint(tblResolution) print(" ",tblResolution[1][1]) print(" ",tblResolution[1][2]) print(" ") -- Check to see the node is connected tothe pin --resNode = octane.node.getInputNode(nFilmSettings_Temp, octane.P_RESOLUTION) --print(resNode.name) --resNode:setAttribute(octane.A_VALUE, {tblResolution[1][1]), tblResolution[1][2]), 0}, false) print("Setting Resolution Attribute on", nImageResolution_Temp.name) nImageResolution_Temp:setAttribute(octane.A_VALUE, {tblResolution[1][1], tblResolution[1][2], 0}, false) print("Resolution set to:") value = nImageResolution_Temp:getAttribute(octane.A_VALUE) print(unpack(value)) -- Why doesn't this actually change the pin value. nFilmSettings_Temp:setPinValue(octane.P_RESOLUTION , tblResolution ,true) print("Resulting Resolution of Node:", nFilmSettings_Temp.name) tprint(nFilmSettings_Temp:getPinValue(octane.P_RESOLUTION)) --octane.node.setPinValue(nFilmSettings_Temp,octane.P_RESOLUTION,tblResolution,true) --print("Resulting Resolution of Node:", nFilmSettings_Temp.name) --tprint(nFilmSettings_Temp:getPinValue(octane.P_RESOLUTION)) print(" ") print("Equirectangular Camera setup complete") print(" ") end if chkCubemapImage.checked then -- Create a New Cubemap Panoramic Camera nCamera_Temp = octane.node.create{type = octane.NT_CAM_PANORAMIC, name = "Temporary Panoramic Cubemap Camera", position = { posGraphX + 200, posGraphY + 200 }} nCamera_Temp:setPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION), false) nCamera_Temp:setPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET), false) nCamera_Temp:setPinValue(octane.P_FOCAL_LENGTH, boxFocalLength.value, true) nCamera_Temp:setPinValue(octane.P_STEREO_OUTPUT , 3, true) nCamera_Temp:setPinValue(octane.P_CAMERA_MODE , 2, true) nCamera_Temp:setPinValue(octane.P_STEREO_DIST , strIPD, true) print("Cubemap Camera Settings") print(nCamera_Temp.name .." Focal Length = ".. nCamera_Temp:getPinValue(octane.P_FOCAL_LENGTH)) --print(nCamera_Temp.name .." FOV = ".. nCamera_Temp:getPinValue(octane.P_FOV)) print(nCamera_Temp.name .." Stereo Output = ".. nCamera_Temp:getPinValue(octane.P_STEREO_OUTPUT)) print(nCamera_Temp.name .." Camera Mode = ".. nCamera_Temp:getPinValue(octane.P_CAMERA_MODE)) print(nCamera_Temp.name .." IPD = ".. nCamera_Temp:getPinValue(octane.P_STEREO_DIST )) print(nCamera_Temp.name .." Position = ") tprint(nCamera_Temp:getPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION))) print(nCamera_Temp.name .." Target = ") tprint(nCamera_Temp:getPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET))) print ("-") -- Connect new Camera to original RenderTarget octane.node.connectToIx(renderTarget, 1, nCamera_Temp, true) -- Create New Film Settings table.insert(tableChildren , lblRenderTarget) local tblResolution = {} if cboResolution.selectedIx == 1 then table.insert(tblResolution, {5160, 1600}) end if cboResolution.selectedIx == 2 then table.insert(tblResolution, {80192, 1536}) end if cboResolution.selectedIx == 3 then table.insert(tblResolution, {4096, 2048}) end if cboResolution.selectedIx == 4 then table.insert(tblResolution, {3840, 1920}) end if cboResolution.selectedIx == 5 then table.insert(tblResolution, {2048, 1024}) end if cboResolution.selectedIx == 6 then table.insert(tblResolution, {1024, 512}) end print(nFilmSettings_Temp.name .." Selected Resolution:") tprint(tblResolution) print(" ") -- Create New Film Settings nFilmSettings_Temp:setPinValue(octane.P_RESOLUTION , {18432, 1536} ,true) print(nFilmSettings_Temp.name) -- Connect Camera to original RenderTarget rv = octane.node.connectToIx(renderTarget, 5, nFilmSettings_Temp, true) print(rv) print("Cubemap Camera setup complete") end if chkSBSImage.checked then -- Create a New Cubemap Panoramic Camera nCamera_Temp = octane.node.create{type = octane.NT_CAM_THINLENS, name = "Temporary Thin Lens Camera", position = { posGraphX + 200, posGraphY + 200 }} nCamera_Temp:setPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION), false) nCamera_Temp:setPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET), false) nCamera_Temp:setPinValue(octane.P_FOCAL_LENGTH, boxFocalLength.value, true) nCamera_Temp:setPinValue(octane.P_STEREO_OUTPUT , 3, true) --nCamera_Temp:setPinValue(octane.P_CAMERA_MODE , 1, true) nCamera_Temp:setPinValue(octane.P_STEREO_DIST , strIPD, true) print(" SBS Camera Settings") print(nCamera_Temp.name .." Focal Length = ".. nCamera_Temp:getPinValue(octane.P_FOCAL_LENGTH)) --print(nCamera_Temp.name .." FOV = ".. nCamera_Temp:getPinValue(octane.P_FOV)) print(nCamera_Temp.name .." Stereo Output = ".. nCamera_Temp:getPinValue(octane.P_STEREO_OUTPUT)) --print(nCamera_Temp.name .." Camera Mode = ".. nCamera_Temp:getPinValue(octane.P_CAMERA_MODE)) print(nCamera_Temp.name .." IPD = ".. nCamera_Temp:getPinValue(octane.P_STEREO_DIST )) print(nCamera_Temp.name .." Position = ") tprint(nCamera_Temp:getPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_POSITION))) print(nCamera_Temp.name .." Target = ") tprint(nCamera_Temp:getPinValue(octane.P_TARGET, renderTarget:getConnectedNode(octane.P_CAMERA):getPinValue(octane.P_TARGET))) print ("-") -- Connect new Camera to original RenderTarget octane.node.connectToIx(renderTarget, 1, nCamera_Temp, true) -- Create New Film Settings table.insert(tableChildren , lblRenderTarget) local tblResolution = {} if cboResolution.selectedIx == 1 then table.insert(tblResolution, {5160, 1600}) end if cboResolution.selectedIx == 2 then table.insert(tblResolution, {80192, 1536}) end if cboResolution.selectedIx == 3 then table.insert(tblResolution, {4096, 2048}) end if cboResolution.selectedIx == 4 then table.insert(tblResolution, {3840, 1920}) end if cboResolution.selectedIx == 5 then table.insert(tblResolution, {2048, 1024}) end if cboResolution.selectedIx == 6 then table.insert(tblResolution, {1024, 512}) end print(nFilmSettings_Temp.name .." Selected Resolution:") tprint(tblResolution) print(" ") nFilmSettings_Temp:setPinValue(octane.P_RESOLUTION , tblResolution ,true) -- Create New Film Settings nFilmSettings_Temp:setPinValue(octane.P_RESOLUTION , {5160, 1600} ,true) print(nFilmSettings_Temp.name) -- Connect Camera to original RenderTarget rv = octane.node.connectToIx(renderTarget, 5, nFilmSettings_Temp, true) print(rv) print("SBS Camera setup complete") end print("End Camera setup") -- Start rendering the current render target print(" ") print("Rendering current render target...") octane.render.start{renderTargetNode = renderTarget, maxRenderTime = mrt} print("Rendering current render target complete") -- Calculate the elapsed time local ImageElapsedTime = string.format("%.02f", os.clock() - ImageStartTime) local strImageLog = "" print(" ") print("Adding record to Log file") -- Add image info to log file string if (os.clock() - ImageStartTime) < boxMaxTime.value*60 then strImageLog = string.format(renderTarget.name .. ", exported with the " .. KernelType .." @" .. kernelNode:getPinValue(octane.P_MAX_SAMPLES) .." samples/pixel in " .. string.format("%.03f",ImageElapsedTime/60) .." mins") else strImageLog = string.format(renderTarget.name .. ", exported with the " .. KernelType .." for " .. string.format("%.03f",mrt/60) .." minutes max. in " .. string.format("%.03f",ImageElapsedTime/60) .." mins") end strLog = strLog .. "\n" .. strImageLog print("Record added") -- Save out the image -- create an output path for the image if settings.outputDirectory then -- common extension for our image output types local fileExtensions = { [octane.imageSaveType.PNG8] = "png", [octane.imageSaveType.PNG16] = "png", [octane.imageSaveType.EXR] = "exr", [octane.imageSaveType.EXRTONEMAPPED] = "exr", } path = string.format("%s/%s", settings.outputDirectory .. "\\" .. txtEditorTourTitle.text, renderTarget.name, kernelNodeMaxSamples, ix, fileExtensions[settings.fileType[ix]]) end print("-") print("Image output path:",path) if path then -- Save the image to the file. rv = octane.render.saveImage(path, settings.fileType[ix]) if rv then print("File #", ix, "saved") numImagesSaved = ix else print("File #", ix, "not saved") end end -- Set the kernal node back to its original number of samples kernelNode:setPinValue(octane.P_MAX_SAMPLES, kernelNodeMaxSamples, true) --nCamera_Temp:setPinValue(octane.P_POSITION, renderTarget:getConnectedNode(octane.P_MAX_SAMPLES):getPinValue(octane.P_POSITION), false) if chkRetainTemporaryNodes.checked == false then print("-") print("Clean up") print(" Destroying temporary Camera and temporary Film Settings") octane.node.destroy(nCamera_Temp) octane.node.destroy(nFilmSettings_Temp) octane.node.destroy(nImageResolution_Temp) end print(" Reconnecting original Camera and original Film Settings") octane.node.connectToIx(renderTarget, 1, cameraOriginal, true) octane.node.connectToIx(renderTarget, 5, filmsettingsOriginal, true) octane.node.connectToIx(filmsettingsOriginal, 1, imageresolutionOriginal, true) print("Cleanup complete") print(" ") -- Update the progress bar progressBar.progress = progressBar.progress + progressBarStep end -- for(ix) loop - i.e. rendertargets loop print("All Rendings completed", numImagesSaved, "Image File(s) saved.") if chkCreatePackage.checked == true then print ("-") print ("Copying resource files to output folder") -- Copy resources to Tour Title directory pathResources = [[C:\Program Files\OTOY\Scripts\Recources]] print ("") pathSourceFile = pathResources .."\\".. "next_arrowglow.png" pathDestinationFile = string.format("%s/%s", settings.outputDirectory, txtEditorTourTitle.text .."\\".. "next_arrowglow.png") rv = octane.file.copy( pathSourceFile, pathDestinationFile ) if rv then print(" File next_arrowglow.png copied") end pathDestinationFile = string.format("%s/%s", settings.outputDirectory, txtEditorTourTitle.text.. "\\" .. "end_arrowglow.png") rv = octane.file.copy( pathSourceFile, pathDestinationFile ) if rv then print(" File end_arrowglow.png copied") end pathDestinationFile = string.format("%s/%s", settings.outputDirectory, txtEditorTourTitle.text.. "\\" .. "ambience.wav") rv = octane.file.copy( pathSourceFile, pathDestinationFile ) if rv then print(" File ambience.wav copied") end print ("Copy complete") print ("-") -- Write out .lua script if path ~= nil then print ("Writing Tour script to specified path") -- Append tabs and new lines to end of .lua script strVRTour = strVRTour .. "\t\t\t" .. " }" .. "\n\t" .. " }" local VRTourFileName = txtEditorTourTitle.text .. ".lua" path = string.format("%s/%s", settings.outputDirectory, txtEditorTourTitle.text .. "\\" .. VRTourFileName) print("VR Tour.lua path:",path) -- Write .lua script to a text file. local file = io.open(path, "w") file:write(strVRTour) file:close() print ("Writing Tour script complete") end end -- Write out .log file if path ~= nil then print(" ") print("Writing log file.") local logFileName = "Octane VR Tour.log" path = string.format("%s/%s", settings.outputDirectory, logFileName ) print(" Log file path:") print(path) -- Write log info to a text file. local file = io.open(path, "w") file:write(strLog) file:close() print("Writing log file complete.") end print(" ") print("ORBX Media Player package complete.") end -- Initialise GUI print("Initializing GUI") setEnabled(wndMain, true) btnCancel.enable = false -- Ths must be placed such that all required functions are read prior to execution. -- The script will hold here until the window closes wndMain:showWindow()