Page 2 of 4

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 4:28 pm
by bepeg4d
ok, thanks to the second part of the turntable tutorial, i was able to identify the missing part for choosing the output path and to integrated it in the animation script:

Code: Select all

-- gather some info about the stuff to render

local root = octane.nodegraph.getRootGraph()
local alltargets = root:findNodes(octane.NT_RENDERTARGET)
if #alltargets == 0 then
    error("Please set up your render target in the main graph.", 0)
end

local rtnode  = alltargets[1]

local interval = root:getAnimationTimeSpan()

-- cancel flag and other globals

isCanceled = false
startT = 0
endT = 0
dT = 0
currentT = 0

-- the actual rendering is quite easy.

-- Render callback

function renderCallback(result)
    if isCanceled then
        octane.render.stop()
    end
    
    local t = currentT + dT * result.samples / result.maxSamples
    t = (t - startT) / (endT - startT)
    progressBar:updateProperties{progress = t}
end

-- Render main loop

function render()
    startT = startSlider:getProperties().value
    endT = endSlider:getProperties().value
    dT = 1 / fpsSlider:getProperties().value
    
    if endT < startT then return end

    local file = fileEditor:getProperties().text
    
    -- check if we have a file name given
    local prefix, sequenceMatch, suffix
    
    -- If we have a file name, check where to substitute the sequence number:
    -- pattern tutorial here: http://lua-users.org/wiki/PatternsTutorial
    if file ~= "" then
    
        -- strip png extension
        file = file:gsub("%.png$", "")

        -- split file into prefix, sequence number and suffix
        -- make sure the sequence number is in the final file name part
        prefix, sequenceMatch, suffix = file:match("(.-)(%d+)([^\\/]*)$")
        
        -- if the file name doesn't contain a sequence number, the match fails
        -- so just assume it is all prefix.
        if sequenceMatch == nil then
            prefix = file
            sequenceMatch = "0000"
            suffix = ""
        end

        -- pattern for string.format.
        seqPattern = "%0"..sequenceMatch:len().."d"
        
        -- add png extension
        suffix = suffix..".png"
        
        -- display resulting file name
        fileEditor:updateProperties{text = prefix..sequenceMatch..suffix}
    end

    currentT = startT
    local seq = 0
    
    -- careful with rounding errors
    while currentT < endT + .001 * dT do
        -- time and sequence number
        seq = seq + 1
        root:updateTime(currentT)

        -- render
        local result = octane.render.start
        {
            renderTargetNode = rtnode,
            callback = renderCallback,
            maxSamples = samplesSlider:getProperties().value
        }
        
        if isCanceled then
            -- canceled
            progressBar:updateProperties{progress = 0}
            return
        elseif file ~= "" then
            -- see if a file was given, and replace the sequence number
            local thisFile = prefix..seqPattern:format(seq)..suffix
            fileEditor:updateProperties{text = thisFile}
            octane.render.saveImage(thisFile, octane.render.imageType.PNG8)
        end
        
        -- go to next time stamp
        currentT = currentT + dT
    end
    
    -- set the progress bar to 100%. 1.0 activates indefinite mode, so...
    progressBar:updateProperties{progress = .99999}
end

-- creates a text label 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  = 100,                               -- width of the label in pixels
        height = 24,                                -- height of the label in pixels
    }
end

-- creates a slider and returns it
local function createSlider(value, min, max, step)
    return octane.gui.create
    {
        type     = octane.gui.componentType.SLIDER, -- type of the component
        width    = 400,                             -- width of the slider in pixels
        height   = 20,                              -- height of the slider in pixels
        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
    }
end

-- lets create a bunch of labels and sliders
local startLbl    = createLabel("Start time")
local endLbl      = createLabel("End time")
local fpsLbl      = createLabel("Framerate")
local shutterLbl  = createLabel("Shutter time")
local samplesLbl  = createLabel("Samples/px")

startSlider   = createSlider(interval[1], interval[1], interval[2], .001)
endSlider     = createSlider(interval[2], interval[1], interval[2], .001)
fpsSlider     = createSlider(25 , 1, 120   , 1)
shutterSlider = createSlider(0, 0, 1, 1/25)
samplesSlider = createSlider(400, 1 , 16000, 1)
samplesSlider:updateProperties{logarithmic = true}

-- manual layouting is tedious so let's add all our stuff in a group.
local settingsGrp = octane.gui.create
{
    type     = octane.gui.componentType.GROUP,  -- type of component
    text     = "Settings",                      -- title for the group
    rows     = 7,                               -- number of rows in the grid
    cols     = 2,                               -- number of colums in the grid
    -- the children is a list of child component that go in each cell. The cells
    -- are filled left to right, top to bottom. I just formatted the list to show
    -- where each component goes in the grid.
    children =
    {
        startLbl      , startSlider   ,
        endLbl        , endSlider     ,
        fpsLbl        , fpsSlider     ,
        shutterLbl    , shutterSlider ,
        samplesLbl    , samplesSlider ,
    },
    padding  = { 2 },   -- internal padding in each cell
    inset    = { 5 },   -- inset of the group component itself
}

-- create a button to show a file chooser
local fileChooseButton = octane.gui.create
{
    type     = octane.gui.componentType.BUTTON,
    text     = "Output...",
    width    = 80,
    height   = 20,
}

-- create an editor that will show the chosen file path
fileEditor = octane.gui.create
{
    type    = octane.gui.componentType.TEXT_EDITOR,
    text    = "",
    x       = 20,
    width   = 400,
    height  = 20,
}

-- for layouting the button and the editor we use a group
local fileGrp = octane.gui.create
{
    type     = octane.gui.componentType.GROUP,
    text     = "Output",
    rows     = 1,
    cols     = 2,
    children =
    {
        fileChooseButton, fileEditor,
    },
    padding  = { 2 },
    inset    = { 5 },
}

-- eye candy, a progress bar
progressBar = octane.gui.create
{
    type   = octane.gui.componentType.PROGRESS_BAR,
    text   = "render progress",
    width  = fileGrp:getProperties().width * 0.8, -- as wide as the group above
    height = 20,
}

-- for layouting the progress bar
local progressGrp = octane.gui.create
{
    type     = octane.gui.componentType.GROUP,
    text     = "",
    rows     = 1,
    cols     = 1,
    children = { progressBar },
    padding  = { 10 },
    centre   = true,    -- centre the progress bar in it's cell
    border   = false,
}

-- render & cancel buttons

local renderButton = octane.gui.create
{
    type   = octane.gui.componentType.BUTTON,
    text   = "Render",
    width  = 80,
    height = 20,
}


local cancelButton = octane.gui.create
{
    type   = octane.gui.componentType.BUTTON,
    text   = "Cancel",
    width  = 80,
    height = 20,
    enable = false,
}

local buttonGrp = octane.gui.create
{
    type     = octane.gui.componentType.GROUP,
    text     = "",
    rows     = 1,
    cols     = 2,
    children = { renderButton, cancelButton },
    padding  = { 5 },
    border   = false,
}

-- group that layouts the other groups
local layoutGrp = octane.gui.create
{
    type     = octane.gui.componentType.GROUP,
    text     = "",
    rows     = 4,
    cols     = 1,
    children =
    {
        settingsGrp,
        fileGrp,
        progressGrp,
        buttonGrp
    },
    centre   = true,
    padding  = { 2 },
    border   = false,
    debug    = false, -- true to show the outlines of the group, handy
}

-- window that holds all components
local animationWindow = octane.gui.create
{
    type     = octane.gui.componentType.WINDOW,
    text     = "Render Animation",
    children = { layoutGrp },
    width    = layoutGrp:getProperties().width, -- same dimensions as the layout group
    height   = layoutGrp:getProperties().height,
}

-- gui callback
 -- Main Flow

    -- global variable that holds the output path
    OUT_PATH = nil

function guiCallback(component, event)
    if event == octane.gui.eventType.BUTTON_CLICKED then
        if component == fileChooseButton then
		-- choose an output file
            local ret = octane.gui.showDialog
            {
                type      = octane.gui.dialogType.FILE_DIALOG,
                title     = "Choose the output file",
                wildcards = "*.png",
                save      = true,
            }
            -- if a file is chosen
            if ret.result ~= "" then
                renderButton:updateProperties{ enable = true }
                fileEditor:updateProperties{ text = ret.result }
                OUT_PATH = ret.result
            else
                renderButton:updateProperties{ enable = false }
                fileEditor:updateProperties{ text = "" }
                OUT_PATH = nil
            end
            
        elseif component == renderButton then
            isCanceled = false
            renderButton:updateProperties{enable = false}
            cancelButton:updateProperties{enable = true}
            render()
            renderButton:updateProperties{enable = true}
            cancelButton:updateProperties{enable = false}
        elseif component == cancelButton then
            isCanceled = true
        end
    elseif event == octane.gui.eventType.WINDOW_CLOSE then
        isCanceled = true
    end
end

-- hookup the callback with all the GUI elements
fileChooseButton:updateProperties { callback = guiCallback }
renderButton:updateProperties     { callback = guiCallback }
cancelButton:updateProperties     { callback = guiCallback }
animationWindow:updateProperties  { callback = guiCallback }

animationWindow:showWindow()
thanks thomas and roeland, now rendering and saving alembic animation is super easy :)
ciao beppe

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 8:51 pm
by stratified
Great work bepe! I think heaps of users will find this usefull.

cheers,
Thomas

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 8:55 pm
by pixelrush
Could we allow Lua attachments in the forum please.

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 10:58 pm
by stratified
We'll try to make that happen.

cheers,
Thomas

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 11:01 pm
by stratified
actually it's already possible
turntable.lua
turntable animation script
(16.49 KiB) Downloaded 347 times

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 11:05 pm
by p3taoctane
Awesome
Thanks for sharing
Beppe any chance you can share the script that renders out an abc file also

Thanks

Peter

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 11:25 pm
by bepeg4d
p3taoctane wrote:Awesome
Thanks for sharing
Beppe any chance you can share the script that renders out an abc file also

Thanks

Peter
sure ;)
ciao beppe

Re: Script to render imported animations

Posted: Fri Dec 20, 2013 11:28 pm
by p3taoctane
Awesome thanks mate

Peter

Re: Script to render imported animations

Posted: Sat Dec 21, 2013 2:34 am
by Tugpsx
Scripting coming along nicely. thanks all for sharing.

Re: Script to render imported animations

Posted: Sat Dec 21, 2013 8:23 am
by bepeg4d
hi, i'm having a big fun with this script :D
i have tested it also under os x with 1.22 and it works smoothly, except for the fact that the script window is not on top and you need to move the octane window to show it.
need to test with 1.23 when it will work under os x again ;)
just a little request: it's possible to add a radio button for reversing the sequence in order to start the render from the last frame instead of the first?
in this way we can render the same sequence with two machines in the same time without worrying about the subdivision of the frames ;)
ciao beppe