Pyjea Custom Nodes

Custom node plugins must be placed in the .panjea folder inside your home directory. Each plugin should start with the name pyjea_. Plugins run inside the Python2 subprocess, but they can contain exported functions that run in the main Blender process (Python3). A plugin must define a function called register that takes the argument api. To register your custom node class, call the function: api["register_node"] passing it your class. Note that all plugins share the same namespace, so make sure you provide unique class and function names.

Example Plugin

Below is an example of a minimal custom node, and how to register it. Your class should have: an icon symbol, a title, inputs list, __init__ method, and eval method.


			import math
			class mynode( object ):
				icon = u'√'
				title = 'Square Root'
				inputs = [ (1,'number') ]
				def __init__(self, node):
					node(self)
				def eval(self, number):
					return math.sqrt( number )
			def register( api ):
				api['register_node']( mynode )
			

pyjea_example_plugin.py

Blender Functions

Blender functions are registered when your node is created, inside your __init__ constructor call the node object passing your function with the keyword argument bpy=myfunc. The call to node will return a signal function wrapper that you can use in your eval method to call the Blender function. Blender functions can use the Blender bpy API directly, and call exported functions from other plugins, but they can not reference global variables from your plugin script. In general, your Blender function should take as its first argument a name, which is the name of the Blender object you are creating or updating, then your function should check if that name is already in bpy.data.objects, if so then update it, otherwise create it. If your function creates many meshes, you should join them together into a single mesh as the last step, this way it remains compatible with the Instance Node.

Tree Example


pyjea_example_tree.py
			class myobject( object ):
				icon = u'🌲'
				title = 'Tree'
				inputs = [(2,'name'), (1,'x'), (1,'y'), (1,'z')]
				def __init__(self, node):
					self.signal = node(self, bpy=make_tree)
				def eval(self, name, x,y,z):
					self.signal( name, x,y,z )
					return name
			

Above is the class which creates the custom node, inside of __init__ the make_tree function is passed to the node using the bpy keyword. The make_tree function runs in the Blender process, and calls the Blender Python API (bpy). Below is the source of the make_tree function, these are all standard bpy calls, except for api.select this is used because it allows the code to be compatible with Blender2.79 and Blender2.8 and newer.

			def make_tree(name, x,y,z):
				if name in bpy.data.objects:
					ob = bpy.data.objects[ name ]
					ob.location = [x,y,z]
					return
				bpy.ops.mesh.primitive_cone_add()
				top = bpy.context.active_object
				top.location = [x,y,z+2]
				mat = bpy.data.materials.new(name=name)
				top.data.materials.append(mat)
				mat.diffuse_color[0] = 0
				mat.diffuse_color[1] = 1
				mat.diffuse_color[2] = 0
				bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=0.2)
				trunk = bpy.context.active_object
				trunk.name = name
				trunk.location = [x,y,z]
				mat = bpy.data.materials.new(name=name+'.trunk')
				trunk.data.materials.append(mat)
				mat.diffuse_color[0] = 0.25
				mat.diffuse_color[1] = 0.25
				mat.diffuse_color[2] = 0.1
				api.select(top)
				bpy.ops.object.join()
			

The bpy API has three main parts that most plugins will use: bpy.ops, bpy.context, and bpy.data. The api object is a simpler abstraction layer on top of these interfaces, and stays backwards compatible with Blender2.79.

bpy.ops

bpy.ops contains operator functions, unlike most functions they do not return the data they are operating on, and instead set Blender into a new state. From this new state you need to fetch what you need, often this is bpy.context.active_object.

bpy.context

bpy.context contains the current state of Blender, normally you will get or set data to it before or after using bpy.ops operators. Because this part of the bpy API has seen major changes since Blender2.79, you should use the api object functions instead of using bpy.context directly.

bpy.data

bpy.data contains all data in Blender, this API is stable, and you will often use it directly. Objects can be referenced by name or index in bpy.data.objects. Mesh instance data is inside bpy.data.meshes. Materials are inside of bpy.data.materials.

Tile Node

To make your custom plugin compatible with the Tile Node you need to follow these rules:

The tree plugin example above can be made compatible like this:
def make_tree(x=None,y=None,z=None, name='tree'):
Note that x,y,z and have None defaults, this allows the tile to set the x,y positions, while allowing your node input to override the z. To use this style you need to set the location using our wrapper method, like this:
api[object].location(x,y,z)

More Examples