Well, easy is a relative concept but if I can write them then a baboon should also be able to do it.
The concepts are really straightforward: a logic cell is something that has an “evaluate()” method, in that method it sets its status to WAITING or READY. The first means that the cell is waiting for the result of another cell, the second that the cell has finished its computation and has set its values.
This is the cell that checks if the mouse is pressed:
class ConditionMousePressed(ConditionCell): def __init__(self):
self.pulse = False
self.mouse_button_code = None
self.network = None
def setup(self, network):
self.network = network
mouse_button = self.get_parameter_value(self.mouse_button_code)
if mouse_button is LogicNetworkCell.STATUS_WAITING: return
mstat = self.network.mouse_events[mouse_button]
self._set_value(mstat == bge.logic.KX_INPUT_JUST_ACTIVATED or mstat == bge.logic.KX_INPUT_ACTIVE)
self._set_value(mstat == bge.logic.KX_INPUT_JUST_ACTIVATED)
The pattern is always the same. get_parameter_value(self.a_field), if the returned value is STATUS_WAITING it means that the value is not available yet and the cell must wait.
When all the requested fields, passed through get_parameter_value, return something other than STATUS_WAITING, the cell can do its computation, set itself to ready and refresh its value with self.set_value(something).
And just to give the entire framework, if a cell spits more than one value, it has to use SubCells to wrap them.
This cell takes a vector and three numbers and produces a vector and three numbers:
self.input_vector = None
self.input_x = None
self.input_y = None
self.input_z = None
self.output_vector = mathutils.Vector()
self.OUTX = LogicNetworkSubCell(self, self.get_out_x)
self.OUTY = LogicNetworkSubCell(self, self.get_out_y)
self.OUTZ = LogicNetworkSubCell(self, self.get_out_z)
self.OUTV = LogicNetworkSubCell(self, self.get_out_v)
def get_out_x(self): return self.output_vector.x
def get_out_y(self): return self.output_vector.y
def get_out_z(self): return self.output_vector.z
def get_out_v(self): return self.output_vector
x = self.get_parameter_value(self.input_x)
y = self.get_parameter_value(self.input_y)
z = self.get_parameter_value(self.input_z)
v = self.get_parameter_value(self.input_vector)
if x is LogicNetworkCell.STATUS_WAITING: return
if y is LogicNetworkCell.STATUS_WAITING: return
if z is LogicNetworkCell.STATUS_WAITING: return
if v is LogicNetworkCell.STATUS_WAITING: return
if v is not None: self.output_vector[:] = v#TODO: zero vector if v is None?
if x is not None: self.output_vector.x = x
if y is not None: self.output_vector.y = y
if z is not None: self.output_vector.z = z
This is as complicate as it gets, on the logic side.
The Nodes are trickier. They have to be what blender wants them to be, to work as nodes in the interface. That requires to know how to write a blender node (this monkey did it, so it’s no big deal).
Then there is the implementation of what the addon needs to transform the node into a cell. That’s not the best thing in the world, I made it up while I was writing it so it doesn’t look much coherent, but it boils down to:
- returning the name of a class from an overridden method
- returning the name of the fields associated with input and output sockets, if any
- returning, in text form, the value of unconnected input sockets, if any
- returning the names of the subcells associated to output sockets, if any
- returning the names and the values, in text form, of the custom properties, if any
Lots of names, but not much else. The reason for all that name mapping is that the addon simply translates the node into the python code needed to instantiate a cell and set its fields. So if a Node represents a Cell of type X, with three input values and two output values, the addon will write - through the names supplied by the node - something like:
CELL = X()
CELL.name_a = something
CELL.name_b = something
CELL.name_c = something
where “something” is either the name of another cell or a constant expression (1.0, “hello world”, bge.events.LEFTMOUSE, things like that).
I don’t post the code of a Node because I tried like five different ways to make it “easy” before to find the one that I like more and I still have to refactor it in.
I will, of course, publish a small tutorial on how to do all this stuff, IF the addon ever sees the light: everything is going according to the plan (that I never had) but you never know when a showstopper will pop out. The UI really made me mad.