Table of contents > Nodes with variable parameters and custom outputs

Nodes with variable parameters and custom outputs

In this chapter we present ways by which you can define nodes with variable parameters and/or custom outputs. Just like we said in the previous chapters, define your nodes in your own preferred text editor or IDE and remember to organize them into categories inside a node pack.

Defining variable parameters

Python allows callables to receive multiple arguments in some special parameters, called variable parameters. Such parameters can be positional-variable, when they can receive multiple positional arguments, or keyword-variable, when they can receive multiple keyword arguments.

Positional-variable parameters are defined by placing an asterisk character ('*') at the beginning of the parameter's name. Keyword-variable parameters use two asterisks instead ('**').

### node with variable arguments

### function definition

def execute_and_return(a_callable, *args, **kwargs):
    return a_callable(*args, **kwargs)

### alias it as the main callable
main_callable = execute_and_return
Node generated from the function

Instead of the usual input socket, a variable parameter has a hollow socket called placeholder socket. When an output from another node is linked to this socket by clicking and dragging a connection from that output socket it creates a new input socket automatically where the placeholder socket was and links that output to the new socket, moving the placeholder socket below it. That new input socket represents a new argument for the variable parameter.

Another way to create new arguments for the variable parameters is to click the plus sign ('+') beside the placeholder socket, allowing the user to pick a widget with a value to represent a new argument.

Even when the new argument was created from connecting an output socket to the placeholder socket, a widget can still be attached to the argument by clicking the plus sign beside the input socket.

Input sockets representing arguments from a variable parameter automatically disappear when they have no source of data, that is, if they have no widget or no connection to an outer output socket.

Beside the input sockets from variable parameters there are arrows that can be used to reorder them. For positional-variable parameters, this order is the same in which the arguments are passed to the callable. For keyword-variable parameters the order is not guaranteed, though, as it depends on the internals of the Python version used.

Last but not least, keyword-variable parameters have 02 additional different from positional-variable parameters. First, each argument have an icon key which has no functional purpose but helps as a visual cue to separate the arguments visually and differentiate them from positional-variable arguments.

Second, each argument from a keyword-variable parameter has also a string entry widget attached to it which is used to edit and hold the keyword associated with the argument.

Naming an output

### this node returns distance between 02 points

### function definition

def distance_from_a_to_b(point_a, point_b):

    ## calculate distance in both axes

    x_distance, y_distance = (

      value_a - value_b
      for value_a, value_b in zip(point_a, point_b)

    )

    ## next, the sum of the squared distances
    sum_of_squares = (x_distance) ** 2 + (y_distance) ** 2

    ## then the Euclidian distance, which is equivalent
    ## to the square root of the sum we just calculated;
    ##
    ## note that "number ** .5" equals the square root
    ## of the number
    distance = (sum_of_squares) ** .5

    ## finally return the distance
    return distance

### alias it as the main callable
main_callable = distance_from_a_to_b

Here's the resulting node:

Node generated from the function

This is a pretty simple and straightforward node. However, it's output name is "output", which isn't very descriptive. Wouldn't it be helpful to be able to rename such ouput to express what the return value really is?

This is possible and a single change is needed: define a return annotation describing the name for the input. Here's how it is done:

### the output of this node has a custom name

### function definition;
###
### here's where the only needed change is applied; note
### that the return annotation is a list with a single
### item: a dict describing the name of the output

def distance_from_a_to_b(point_a, point_b) -> [

      {'name': 'euclidean_distance'},

    ]:

    ## calculate distance in both axes

    x_distance, y_distance = (

      value_a - value_b
      for value_a, value_b in zip(point_a, point_b)

    )

    ## next, the sum of the squared distances
    sum_of_squares = (x_distance) ** 2 + (y_distance) ** 2

    ## then the Euclidian distance, which is equivalent
    ## to the square root of the sum we just calculated;
    ##
    ## note that "number ** .5" equals the square root
    ## of the number
    distance = (sum_of_squares) ** .5

    ## finally return the distance
    return distance

### alias it as the main callable
main_callable = distance_from_a_to_b

Now the function properly names its output, the Euclidean distance. Here's what our improved node looks like:

Node generated from the function

Defining a node with more than 01 output

The node presented before produces lots of relevant data besides the returned Euclidean distance. Isn't it a pity, that we have no access to the x_distance and y_distance? That is, the node only has 01 output socket which contains the returned distance.

Fortunately for us, it is both possible and easy to define a node with more than 01 output. In fact, this is a perfect example of when it would be useful.

Only 02 small changes are needed:

  1. list the different outputs in the return annotation of the function;
  2. instead of returning a single value, return a dictionary containing each output listed in the return annotation.

Here's how we'd do it:

### this node returns many kinds of distance between 02
### points

### function definition;
###
### note that the return annotation is a list containing
### dictionaries; each dictionary defines the name of an
### output (this is similar to what we do to rename the
### output, but in this case we provide a dictionary for
### each output);

def distance_from_a_to_b(point_a, point_b) -> [

      {'name': 'distance_in_x_axis'},
      {'name': 'distance_in_y_axis'},
      {'name': 'euclidean_distance'},

    ]:

    ## calculate distance in both axes

    x_distance, y_distance = (

      value_a - value_b
      for value_a, value_b in zip(point_a, point_b)

    )

    ## next, the sum of the squared distances
    sum_of_squares = (x_distance) ** 2 + (y_distance) ** 2

    ## then the distance, which is equivalent to the
    ## square root of the sum we just calculated;
    ##
    ## note that "number ** .5" equals the square root
    ## of the number
    distance = (sum_of_squares) ** .5

    ## finally return the distances inside a dict using
    ## the names defined in the return annotation; the
    ## order isn't relevant because the node uses the
    ## order of the items in the list from the return
    ## annotation;

    return {

      'distance_in_x_axis': x_distance,
      'distance_in_y_axis': y_distance,
      'euclidean_distance': distance,

    }

### alias it as the main callable
main_callable = distance_from_a_to_b

And here's the resulting node:

Node generated from the function

Previous chapter | Table of contents | Next chapter