Page 1 of 1

Creating a scripted node graph

PostPosted: Fri Sep 26, 2014 5:27 am
by roeland
Octane 2.10 introduced a new feature for scripting, called a "scripted graph". This is a node graph with an embedded script, which will create all the contents. It can change the contents in response to changes to the input value, or the scene time.

The basic skeleton of a script in such a graph looks like this:

Code: Select all
    local MyGraphScript = {}

    function MyGraphScript.onInit(self, graph)

    function MyGraphScript.onEvaluate(self, graph)
        return true

    return MyGraphScript

Running this script will not do any work, but it will create a table (called MyGraphScript), fill in some values and return the table. Octane will then add a metatable to this table, so you can call the methods from the octane.scriptgraph using the colon syntax.

onInit is a function, it's called once after the graph is loaded, or after the script is changed. It receives two arguments: the first argument is the table which was returned from this script, the second one is the node graph.

onEvaluate will be called every time one of the input values changes. As with any node graph, the inputs are defined by input linker nodes. It should usually return true, it should return false only if you decide that an input change doesn't have any effect.

So let's now create a script which actually does something. We will create one texture input which should get an RGB color, and one output which is the color with the hue rotated 120°.

First we create the table we are going to return:
Code: Select all
local MyGraphScript = {}

Now, the onInit function will create all the nodes in the graph. We need some of these in our onEvaluate function to update values. One way to keep references is declaring some references in the scope of our script.
Code: Select all
    -- variables declared in the script scope are visible for all functions in our
    -- script for as long as the scripted graph is not deleted or reloaded (in
    -- programming terms, our two functions become "closures").
    local inputs, tex

    -- onInit function, this is called once in the beginning.
    function MyGraphScript.onInit(self, graph)
        -- input and output infos
        local inputInfos = {
            {type=octane.PT_TEXTURE, label="RGB", defaultNodeType=octane.NT_TEX_RGB}
        local outputInfos = {
            {type=octane.PT_TEXTURE, label="RGB"}
        -- use these functions to set up input and output linkers. This will keep
        -- existing linkers so existing connections in the parent graph are kept.
        inputs = graph:setInputLinkers(inputInfos)
        local outputs = graph:setOutputLinkers(outputInfos)
        -- set up a node to give the graph some output value
        tex = octane.node.create{ type=octane.NT_TEX_RGB, name="color", graphOwner=graph }
        outputs[1]:connectTo("input", tex)

Let's go over this step by step:

Before the onInit function is called, Octane will always make sure the node graph is empty, except for the linker nodes. Keeping the linker nodes ensures that if there are nodes connected to the scripted graph, these connections are not broken by reloading the script. For this reason the script should not destroy and create the linkers. Therefore the API provides two new functions in the octane.nodegraph module to set up linker nodes, setInputLinkers and setOutputLinkers.

First we define two arrays of tables, inputInfos and outputInfos, which specify which linker nodes we want. The minimal information is the node pin type, and the label for the input or output. For input linkers you can also specify extra information, like the default node type, the minimum and maximum values etc. For value inputs (int, float, bool, transform and texture) you should always at least specify the default node type.

Then we call the setInputLinkers and setOutputLinkers functions, to set up the linker nodes. These functions will automatically keep existing linkers if possible. They return the list of linker nodes.

Finally we create an RGB texture node and connect it to the output linker.

The onEvaluate function is called whenever the input value of our input linker changes:

Code: Select all
    -- this function is called every time the value of an input linker changes
    function MyGraphScript.onEvaluate(self, graph)
        -- the scriptgraph object has a special function to read input values
        local rgb = self:getInputValue(inputs[1])
        -- set some output value. (for example, rotate the RGB channels)
        tex:setAttribute("value", {rgb[3], rgb[1], rgb[2]})

This function reads the value from the input linker. Note that normally you can't read a value via the input pin of a linker node. In scripted graphs you can use the getInputValue function, and it updates the value attribute on the RGB texture node.

So, finally, what's up with these inputs and tex variables? These are declared in the scope of the script. Remember that the script is just run in the beginning to set up the table, so these variables go out of scope after we return the table. But we use the variables in the onInit and onEvaluate functions. When this happens, Lua will keep the values so the functions can use them if they are called later.

Most of the stuff is documented in the octane.graphscript module except for the pin information, I'll make another post for that.


Creating input pins for a scripted graph

PostPosted: Fri Sep 26, 2014 6:17 am
by roeland
If you specify input pins of a scripted node graph with octane.nodegraph.setInputLinkers, you can give extra information in the pin info tables:

For all types you specify

label: the pin label
type: the pin type
group: the group which the pin belongs to (optional), available for versions >= 3.04
description: the description of the pin (optional), available for versions >= 3.05.1

The other fields depend on the node type. All types listed below have one extra field in common:

defaultNodeType: the type of node which should be created by default for the pin


Code: Select all
{label = "myLabel", type = octane.PT_BOOL, defaultNodeType=octane.NT_BOOL, defaultValue=false}

Bool pins have no extra fields.


Code: Select all
{label = "myLabel", type = octane.PT_ENUM, defaultNodeType=octane.NT_ENUM, defaultValue=1, enum={"apples", "pears"}}

enum: this is a table with integer keys and string values. These define the values the user can select.


Code: Select all
{label = "myLabel", type = octane.PT_INT, defaultNodeType=octane.NT_INT, defaultValue=1, bounds={1, 10}}
{label = "myLabel", type = octane.PT_INT, defaultNodeType=octane.NT_INT, defaultValue={1, 2, 3}, bounds={1, 10}}
{label = "myLabel", type = octane.PT_INT, defaultNodeType=octane.NT_IMAGE_RESOLUTION, defaultValue={1920, 1080}, bounds={100, 32000}}

bounds: The minimum and maximum value for this input. If not specified, the values are unbounded (similar to the translation input of the transform node).
sliderBounds: The minimum and maximum value to use for the slider (leave this one out if it should be the same as the bounds).
logarithmic: if set to true, the UI will show a logarithmic slider for this input. Only supported when the slider minimum is positive.
step: for float pins, specifies the minimum step size for the slider

The number of sliders shown depends on the default value: you may specify a value, or a vector with 2 or 3 elements.


Code: Select all
{label = "myLabel", type = octane.PT_TEXTURE, defaultNodeType=octane.NT_TEX_FLOAT, defaultValue=.5}
{label = "myLabel", type = octane.PT_TEXTURE, defaultNodeType=octane.NT_TEX_RGB, defaultValue={1.0, 0.5, 0}}

The default value is either a float, or a vector of 3 floats


The string type was introduced as an extra type for the scripted graphs. You can use it to have an input specifying either a plain string or a file name.

Code: Select all
{label = "myLabel", type = octane.PT_STRING, defaultNodeType=octane.NT_STRING, defaultValue="", multiLine=false}
{label = "myLabel", type = octane.PT_STRING, defaultNodeType=octane.NT_FILE, filePattern="*.csv"}

multiline: if false then the value can't contain line breaks (ignored if filePattern is set).
filePattern: specifies a file name filter pattern to be used if the user selects a file. Consists of one or more patterns, separated by semicolons (;). The default node type should be octane.NT_FILE.


Code: Select all
{label = "myLabel", type = octane.PT_TRANSFORM, defaultNodeType=octane.NT_TRANSFORM_VALUE, dimCount=3}

dimCount: The number of dimensions for the transform, must be 2 or 3.

Other types

For other pin types, like PT_MATERIAL or PT_GEOMETRY, you only need to specify label and type.

Using an existing node pin info from a node type

Code: Select all
{ type = octane.PT_FLOAT, label="sun power", fromNodeType=octane.NT_ENV_DAYLIGHT, fromPinId=octane.P_POWER }

Use the fromNodeType and fromPinId keys to use a node pin info for a static pin from an existing node type.