Node API¶
Every Node is a subclass of bpy.types.Node and mn_node_base.AnimationNode. A node has at least two properties: bl_idname and bl_label. The id-name should be the class name.
Furthermore every node has an init function that looks like this:
1 2 3 4 5 6 7 | def init(self, context):
forbidCompiling()
self.inputs.new("mn_VectorSocket", "Position")
self.inputs.new("mn_VectorSocket", "Rotation")
self.inputs.new("mn_VectorSocket", "Scale").vector = [1, 1, 1]
self.outputs.new("mn_MatrixSocket", "Matrix")
allowCompiling()
|
This is mostly used to create sockets that a node has after creation.
forbidCompiling() and allowCompiling should always be used to avoid some errors, because so the addon executes the node not until every socket is created.
There are 2 different main methods how a node can be executed:
Execute Function¶
The node has a function called execute, which has the socket values as input and the corresponding output.
The easiest way to use this is the Dictionary-Mode.
Dictionary-Mode:¶
This means that you have only one input variable next to self which is a Dictionary. The keys are the socket identifiers (name). The function must return another dictionary.
1 2 3 4 5 6 7 8 | def execute(self, input):
output = {}
if input["Condition"]:
output["Text"] = "Yes"
else: output["Text"] = "No"
return output
|
With defined socket names:¶
This is a slightly faster method because there is less overhead creating dictionaries. Here you first have to define how your variables are named in the execute function.
1 2 3 4 5 6 7 8 9 10 11 | # example from animate float node
def getInputSocketNames(self):
return {"Start" : "start",
"End" : "end",
"Time" : "time",
"Interpolation" : "interpolation",
"Duration" : "duration",
"Delay" : "delay"}
def getOutputSocketNames(self):
return {"Current" : "current",
"New Time" : "newTime"}
|
The execute function uses these names as parameters. Note that the return order has to be the same like the output socket order.
1 2 3 4 5 | def execute(self, start, end, time, interpolation, duration, delay):
duration = max(duration, 1)
influence = interpolation[0](max(min(time / duration, 1.0), 0.0), interpolation[1])
current = start * (1 - influence) + end * influence
return current, time - duration - delay
|
In-Line Execution¶
As you may know this addon generates a python script based on the node tree. Using this method you can modify this generated Script. This is the fasted execution because there is no function overhead and you can optimize the code a lot. To use that the node has to have at least 4 additional functions.
1 2 3 4 5 6 7 8 9 10 11 12 | # from float clamp node
def getInputSocketNames(self):
return {"Value" : "value",
"Min" : "minValue",
"Max" : "maxValue"}
def getOutputSocketNames(self):
return {"Value" : "value"}
def useInLineExecution(self):
return True
def getInLineExecutionString(self, outputUse):
return "$value$ = min(max(%value%, %minValue%), %maxValue%)"
|
The getInLineExecutionString function returns a string with python code. The variable names are the same that you defined in the 2 functions above. Output variables are surrounded by $ and inputs by %.
The parameter outputUse is for optimizing the code like in this example:
1 2 3 4 5 6 7 8 9 | # from Time Info node
def getInLineExecutionString(self, outputUse):
codeLines = []
codeLines.append("scene = bpy.context.scene")
if outputUse["Frame"]: codeLines.append("$frame$ = scene.frame_current_final")
if outputUse["Start Frame"]: codeLines.append("$start_frame$ = scene.frame_start")
if outputUse["End Frame"]: codeLines.append("$end_frame$ = scene.frame_end")
if outputUse["Frame Rate"]: codeLines.append("$frame_rate$ = scene.render.fps")
return "\n".join(codeLines)
|
If the code you defined there needs extra modules you can define them the following way:
1 2 3 | # from expression node
def getModuleList(self):
return ["math"]
|
Other Features¶
‘Output Use’ parameter for execution function
If the node doesn’t need to calculate every output under certain circumstances and you use the execute - method, you may want to use the outputUseParameterName. It works like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class mn_TextBlockReader(Node, AnimationNode):
bl_idname = "mn_TextBlockReader"
bl_label = "Text Block Reader"
outputUseParameterName = "useOutput"
# ...
def getInputSocketNames(self):
return {"Text Block" : "textBlock"}
def getOutputSocketNames(self):
return {"Text" : "text",
"Lines" : "lines"}
def execute(self, useOutput, textBlock):
text = ""
if textBlock is not None:
text = textBlock.as_string()
if useOutput["Lines"]: return text, text.split("\n")
else: return text, []
|
Determined Nodes
Nodes that always have the same output with the same inputs should be marked with the isDetermined attribute. E.g. the math node but not the object info node, because although the object is the same, the location can be different a millisecond later.
This attribute is mostly used for loops, so that nodes that have the same output in every iteration aren’t calculated multiple times.
1 2 3 4 | class mn_FloatMathNode(Node, AnimationNode):
bl_idname = "mn_FloatMathNode"
bl_label = "Math"
isDetermined = True
|