Python nodes provide a way of extending the set of nodes in the Blender node editor using the API.
Here is a description of the major changes:
Blender has a dynamic registration feature for certain RNA classes, like Operators, Panels, etc. This feature is now also available for node types, as well as socket types and even entire tree types.
The easiest way to register such types is by using Python scripts (although any RNA language binding could do this). In Python it works the same way as with existing registerable types, by defining a subclass of the bpy.types.Node base class:
class MyCustomNode(bpy.types.Node): ...
class MyCustomNodeSocket(bpy.types.NodeSocket): ...
class MyCustomNodeTree(bpy.types.NodeTree): ...
All three new registerable types have a few similar base properties:
class MyCustomNode(bpy.types.Node): # Description string '''A custom node''' # Optional identifier string. If not explicitly defined, the python class name is used. bl_idname = 'CustomNodeType' # Label for nice name display bl_label = 'Custom Node' # Icon identifier bl_icon = 'SOUND'
class MyCustomSocket(bpy.types.NodeSocket): # Description string '''Custom node socket type''' # Optional identifier string. If not explicitly defined, the python class name is used. bl_idname = 'CustomSocketType' # Label for nice name display bl_label = 'Custom Node Socket' # Socket color bl_color = (1.0, 0.4, 0.216, 0.5)
class MyCustomTree(bpy.types.NodeTree): # Description string '''A custom node tree type that will show up in the node editor header''' # Optional identifier string. If not explicitly defined, the python class name is used. bl_idname = 'CustomTreeType' # Label for nice name display bl_label = 'Custom Node Tree' # Icon identifier # Note: If no icon is defined, the node tree will not show up in the editor header! # This can be used to make additional tree types for groups and similar nodes bl_icon = 'NODETREE'
Poll functions are a widely used concept in Blender for determining availability of features based on the context. For example:
- Operator poll is used to test if an operator can be executed in a given context
- Panel/Menu poll determines the visibility
Poll functions are used in different roles throughout Python Nodes as well.
Many node types only work in one type of node system, for example:
- Shader nodes only make sense in a Cycles node tree
- Blur nodes only work in compositor nodes
The node.poll function in Node subclasses is a way of checking compatibility and/or limiting nodes to certain types of trees. Only if this function returns
True can a new node of this type be created in the given tree.
class MyCustomNode(bpy.types.Node): @classmethod def poll(cls, tree): # this node is only available in trees of the 'CustomTreeType' return tree.bl_idname == 'CustomTreeType'
Note that the poll function is a classmethod, so it can be called before an actual instance of this node type is created.
Node Types indepedent from Tree Types
In the current C implementation node types are stored as lists in tree types. This means that node types can not be shared between different tree types, which is a desirable feature (e.g. math nodes are common in many trees and could share the UI implementation). By storing node types completely separate from tree types this limitation is removed. A node is not registered as part of a specific tree type. This limitation must be implemented by the poll function instead.
There is a secondary poll function called 'poll_instance', which is optional and not required for most node types. Unlike the poll function this is a full member function:
class MyCustomNode(bpy.types.Node): def poll_instance(self, tree): return True
poll_instance is used by certain operators which insert nodes into a different tree, such as the node clipboard paste operator or the node group insert operator. One example use case is group nodes, which need to be prevented from being inserted into themselves to avoid infinite recursion. In general the 'poll_instance' function allows you to check the node's settings before allowing it to be inserted.
Node tree poll functions can restrict general access to a tree type based on context. If this function is defined and returns
False, the tree type will not show up in the node editor.
class MyCustomTree(bpy.types.NodeTree): @classmethod def poll(cls, context): # typical shader node test for compatible render engine setting return context.scene.render.engine == 'Cycles'
A typical use case is to check the render engine setting and only show the appropriate shading nodes for that renderer.
Other node functions
Nodes can have a number of optional functions that are called during the lifetime of a node instance:
def init(self, context): print("New node: ", self.name)
Called when a new instance of the node type is created. Useful for initializing node settings from the context.
def copy(self, node): print("New node ", self.name, "copied from", node.name)
Called when a copy of some node is made instead of creating a new instance on its own.
def free(self, context): print("Removing node: ", self.name)
Called when a node is deleted.
def update(self): print("Updating node: ", self.name)
Called whenever the node relations in the editor change, i.e. a connection from/to this node is added or removed. Some advanced nodes may use this e.g. to change their socket layout based on connections.
Almost all nodes will need to store some additional data for parameters. For this purpose nodes now have the ability to store custom properties (see this page for in-depth info about custom properties in general). With the bpy.props module node properties can be registered in the RNA:
class MyCustomNode(bpy.types.Node): # File path property stored in the node myStringProperty = bpy.props.StringProperty(subtype='FILE_PATH', default="//") # Custom enum property to select from a predefined list my_items = [ ("DOWN", "Down", "Where your feet are"), ("UP", "Up", "Where your head should be"), ("LEFT", "Left", "Not right"), ("RIGHT", "Right", "Not left") ] myEnumProperty = bpy.props.EnumProperty(name="Direction", description="Just an example", items=my_items, default='UP')
Node sockets can be added to and removed from nodes at any time. Most nodes will just need to add a static list of sockets when they are created, but in principle it is possible to change the interface to a node whenever needed.
Basic API functions
Node socket layout can be changed directly using one the following functions:
Node.inputs.new(type, name[, identifier]) Node.outputs.new(type, name[, identifier])
Adds an input/output socket respectively. The optional identifier can be used to resolve ambiguity when nodes have several equivalent sockets with the same name. If no identifier string is given explicitly the name string will be used. If a socket with the same identifier already exists, it will be replaced by the new socket.
Remove the input/output socket.
Sockets can be looked up by strings, which will use the socket identifier (defaulting to the socket name).
Socket Input Values
Sockets typically describe some sort of numeric input value of a node. Standard socket types of this kind in Blender include
- Floating point numbers
- RGB/RGBA colors
- 3D Vectors
- Integer numbers
- Boolean options (true/false)
These types of sockets conveniently can display a constant input value, which will be used when the socket is not connected to another node's output.
When created with the API functions described above, however, a new socket will not display a constant input value by default. In order to enable this the socket must be pointed to a property of the node, using the value_property string:
class MyCustomNode(bpy.types.Node): ... # the input value property to use for an unconnected socket my_input_value = bpy.props.FloatProperty(name="Size", default_value=5.0, subtype="Factor") def init(self, context): my_input = self.inputs.new("NodeSocketFloat", "My Input") my_input.value_property = "my_input_value"
To actually display the custom properties of a node there is a draw function which can be implemented in Python. It works the same way as drawing in panels:
class MyCustomNode(bpy.types.Node): def draw_buttons(self, context, layout): layout.prop(self, "myStringProperty") layout.prop(self, "myEnumProperty")
There is a second variant of the draw function which is used for the sidebar panel. This way extended information can be displayed separately if drawing on the node would take too much space or cause unwanted visual noise. The secondary draw function is optional, if undefined the standard draw function will be used in the sidebar.
def draw_buttons_ext(self, context, layout): layout.label("These are extended node options") layout.prop(self, "myStringProperty") layout.prop(self, "myEnumProperty") layout.operator("node.my_custom_operator")
Main way of identifying node is bNodeType->idname string, not bNodeType->type integer. The type id stays for compatibility reasons and to keep old systems working until it can be replaced at later date.
Custom nodes use the NODE_CUSTOM type (value -1) to distinguish them and make functions use idname instead. For static node types (type != NODE_CUSTOM) the NOD_static_types.h header file (previously rna_nodetree_types.h) is used to generate usable idnames.
Registering Custom Node Types
Custom node types can be created using the RNA API functions. Node struct has register/unregister functions defined, which allocate a bNodeType struct and initialize from validation function.
Tree and node types use global hashes now for storing registered types. Node types are not directly stored in tree types any more, to have more flexibility: a node type can be used for multiple different tree types, if necessary.