1. NodeBox 3
    1. Homepage
    2. NodeBox 3Node-based app for generative design and data visualization
    3. NodeBox OpenGLHardware-accelerated cross-platform graphics library
    4. NodeBox 1Generate 2D visuals using Python code (Mac OS X only)
  2. Gallery
  3. Download
  4. Documentation
  5. Forum
  6. Blog

Writing your own Nodes

This is important:

We’re currently rewriting the graphics API to be more performant. Code that access nodebox.graphics will break. Other code that uses generic Python libraries should be okay.

Once you get the hang of it, it becomes fairly easy to build networks and hide lengthy procedures behind a subnetwork facade. However, sometimes there are cases where you wish you could write your own nodes.

Maybe you want to reuse a cool piece of code in NodeBox that you’ve written. Maybe NodeBox is missing some key piece of functionality that you need. Or maybe you just want to experiment with writing a new node from scratch. This tutorial shows you how.

Creating a new node from existing code.

First off, let’s build a node that does something really simple like adding two numbers together. We won’t write our own code yet but we’ll reference some code that already exists inside NodeBox. This is really easy. To get started:

The metadata panel always relates to one specific node, in this case node1. The items on the left relate to node and port settings. What they mean becomes clearer when we have some ports to work with. Since we want to add 2 numbers we need 2 ports:

Node Metadata An overview of node and port settings in the Metadata panel

First Node A node that we made from scratch

You can now drag the value1 and value2 values around and see the results change in the viewer: the two values are added together. If you return to the metadata panel and change the value of Function to math/multiply, you will see that the two values are being multiplied instead.

To summarize:

If you want to, you can rename your ‘node1’ node to something more comprehensible, like ‘add’ or ‘multiply’ (by right clicking and choosing rename)

Adding node with custom Python code.

We’re now going to look how you can link to an external piece of Python code.

def evaluate(expr, x, y, z):
    ns = {"x": x, "y": y, "z": z}
    exec "from math import *" in ns
    return eval(expr, ns)

This snippet evaluates a mathematical expression with at most 3 arguments and returns the result.

Nodes refer to functions

It’s important to keep in mind that NodeBox uses a functional coding model. Every node computes a value from a set of inputs you give it and the same set of inputs will always yield the same value. So we always use a named function (in the above case it’s called evaluate) that takes a certain amount of arguments (here there’s four: expr, x, y and z). In the functions body some work goes on and the function returns one value. Please try to stick to this when you write some code yourself.

NodeBox library structure A typical NodeBox library structure

You can now import the mycode.py file in NodeBox:

Adding a code library

You can now create a node based on this code:

Great! Our node is now fully functional. Let’s try it out with a couple of expressions:

As you can see the expression gives us access to the values of the 3 parameters x, y and z, but also to all the mathematical operations that the python math package provides.

Suppose now that you want to add a 4th expression variable, named a:

def evaluate(expr, x, y, z, a):
    ns = {"x": x, "y": y, "z": z, "a": a}
    exec "from math import *" in ns
    return eval(expr, ns)

Our code has changed but NodeBox is not aware of it yet.

Building a shader node.

Let’s build a node that calculates the brightness of an object at a specified location relative to a light source at another location. The source for this node is taken from the Colors library that was made for NodeBox 1. The source code is written in Python, so we can also use it in NodeBox 3.

from math import degrees, atan2, sqrt
def shader(position, source, radius=300, angle=0, spread=90):

    """ Returns a 0.0 - 1.0 brightness adjusted to a light source.

    The light source is positioned at dx, dy.
    The returned float is calculated for x, y position
    (e.g. an oval at x, y should have this brightness).

    The radius influences the strength of the light,
    angle and spread control the direction of the light.


    if angle != None:
        radius *= 2

    x = position.x
    y = position.y

    dx = source.x
    dy = source.y

    # Get the distance and angle between point and light source.
    d = sqrt((dx-x)**2 + (dy-y)**2)
    a = degrees(atan2(dy-y, dx-x)) + 180

    # If no angle is defined,
    # light is emitted evenly in all directions
    # and carries as far as the defined radius
    # (e.g. like a radial gradient).
    if d <= radius:
        d1 = 1.0 * d / radius
        d1 = 1.0
    if angle == None:
        return 1-d1

    # Normalize the light's direction and spread
    # between 0 and 360.
    angle = 360-angle%360
    spread = max(0, min(spread, 360))
    if spread == 0:
        return 0.0

    # Objects that fall within the spreaded direction
    # of the light are illuminated.
    d = abs(a-angle)
    if d <= spread/2:
        d2 = d / spread + d1
        d2 = 1.0

    # Wrapping from 0 to 360:
    # a light source with a direction of 10 degrees
    # and a spread of 45 degrees illuminates
    # objects between 0 and 35 degrees and 350 and 360 degrees.
    if 360-angle <= spread/2:
        d = abs(360-angle+a)
        if d <= spread/2:
            d2 = d / spread + d1
    # Wrapping from 360 to 0.
    if angle < spread/2:
        d = abs(360+angle-a)
        if d <= spread/2:
            d2 = d / spread + d1

    return 1 - max(0, min(d2, 1))

Note that we have one function here named shader that takes 5 arguments (position, source, radius, angle and spread). The function returns a number between 0 (dark) and 1 (bright).

Now it’s time to create the actual node:

In the parameter panel, give this new shader node the following values:

Custom shader node

The shader node only computes values, let’s create some visual output:

Network part 1 The first part of our network. Shattered circles Circles all over the place after creating the first part of our network.

In the second part of our network we’re going to calculate the level of shading of each individual ellipse:

Network part 2 The second part of our network.

Now that we have a list of circles and a list of colors we can the individual pieces together:

Network part 3 The final network. Final result Our final result.

Play around with different shader settings and color variations to vary your results.