-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e28c334
commit 8299cc0
Showing
13 changed files
with
582 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
title : Developer Guide | ||
weight : 30 | ||
chapter : true | ||
--- | ||
|
||
# Developer Guide | ||
|
||
{{% children %}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
title : Node Development | ||
weight : 10 | ||
chapter : true | ||
--- | ||
|
||
# Node Development | ||
|
||
{{% children %}} |
Binary file added
BIN
+137 KB
content/developer_guide/node_development/dynamic_sockets/generate_points_1.mp4
Binary file not shown.
152 changes: 152 additions & 0 deletions
152
content/developer_guide/node_development/dynamic_sockets/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
--- | ||
title : Dynamic Sockets | ||
weight : 40 | ||
--- | ||
|
||
The development method you've seen so far works great and should always | ||
be used when possible, because it keeps the code clean. However it has | ||
one big disadvantage, it only works when the amount of sockets stays the | ||
same all the time. This is because the `execute` function has a fixed | ||
number of parameters. | ||
|
||
Many a bit more advanced nodes support multiple modes though. E.g. the | ||
*Math* nodes support a lot of different operations and some of these | ||
require different sockets. In order to show how nodes with multiple | ||
operations work, we'll forget about the node we developed in the last | ||
parts and focus on a new one. | ||
|
||
We now want to develop a node called *Generate Points* (maybe you should | ||
try to find a more specific name...) that has two modes: | ||
|
||
- Line: | ||
Takes two vectors (start and direction) and an integer (amount) | ||
as input and calculates a vector list with points on the line. | ||
|
||
- Circle: | ||
Takes a float (radius) and an integer (amount) as input and | ||
calculates a vector list with points on the circle. | ||
|
||
Obviously the old approach doesn't work here anymore because both modes | ||
have different amounts of inputs. But before you'll learn about another | ||
approach on how node execution works in AN, we'll focus on how to create | ||
a node with dynamic sockets in the first place. | ||
|
||
For our example we will use an `EnumProperty` to allow the user to | ||
switch between both modes. Furthermore we have to tell Animation Nodes | ||
that it has to recreate the sockets when the mode changes. There are | ||
many nodes in AN already which have to do this as well. The general | ||
structure of the code looks like so: | ||
|
||
``` python | ||
import bpy | ||
from bpy.props import * | ||
from animation_nodes.base_types import AnimationNode | ||
|
||
# first define the modes the node can have | ||
# item tuple: (identifier, display name, description, icon, value) | ||
# the value should be different for each item and should not change later | ||
modeItems = [ | ||
("LINE", "Line", "Distribute points on line", "", 0), | ||
("CIRCLE", "Circle", "Distribute points on circle", "", 1) | ||
] | ||
|
||
class GeneratePointsNode(bpy.types.Node, AnimationNode): | ||
bl_idname = "an_GeneratePointsNode" | ||
bl_label = "Generate Points" | ||
|
||
# AnimationNode.refresh will cause AN to remove all sockets | ||
# and to call the create method again. | ||
mode = EnumProperty(name = "Mode", default = "LINE", | ||
items = modeItems, update = AnimationNode.refresh) | ||
|
||
def create(self): | ||
self.newInput("Integer", "Amount", "amount") | ||
if self.mode == "LINE": | ||
self.newInput("Vector", "Start", "start") | ||
self.newInput("Vector", "Direction", "direction") | ||
elif self.mode == "CIRCLE": | ||
self.newInput("Float", "Radius", "radius") | ||
self.newOutput("Vector List", "Points", "points") | ||
|
||
def draw(self, layout): | ||
layout.prop(self, "mode") | ||
``` | ||
|
||
So now that you know how to make nodes with changing sockets, it is now | ||
time to show how to write the execution code in that case. The solution | ||
is to define a new function called `getExecutionFunctionName(self)`. It | ||
returns a string that contains the name with the function that should be | ||
called at that moment. Basicly it looks like this: | ||
|
||
``` python | ||
def getExecutionFunctionName(self): | ||
if self.mode == "LINE": | ||
return "execute_Line" | ||
elif self.mode == "CIRCLE": | ||
return "execute_Circle" | ||
|
||
def execute_Line(self, amount, start, radius): | ||
# do something | ||
|
||
def execute_Circle(self, amount, radius): | ||
# do something different | ||
``` | ||
|
||
With that new knowledge it is easy to fill in the missing code in the | ||
correct functions to get the final node. I'll just skip forward to the | ||
end now because how to calculate points on lines and circles is not part | ||
of this tutorial. However here is the code: | ||
|
||
``` python | ||
import bpy | ||
from bpy.props import * | ||
from math import sin, cos, pi | ||
from animation_nodes.data_structures import Vector3DList | ||
from animation_nodes.base_types import AnimationNode | ||
|
||
modeItems = [ | ||
("LINE", "Line", "Distribute points on line", "", 0), | ||
("CIRCLE", "Circle", "Distribute points on circle", "", 1) | ||
] | ||
|
||
class GeneratePointsNode(bpy.types.Node, AnimationNode): | ||
bl_idname = "an_GeneratePointsNode" | ||
bl_label = "Generate Points" | ||
|
||
mode = EnumProperty(name = "Mode", default = "LINE", | ||
items = modeItems, update = AnimationNode.refresh) | ||
|
||
def create(self): | ||
self.newInput("Integer", "Amount", "amount") | ||
if self.mode == "LINE": | ||
self.newInput("Vector", "Start", "start") | ||
self.newInput("Vector", "Direction", "direction") | ||
elif self.mode == "CIRCLE": | ||
self.newInput("Float", "Radius", "radius") | ||
self.newOutput("Vector List", "Points", "points") | ||
|
||
def draw(self, layout): | ||
layout.prop(self, "mode") | ||
|
||
def getExecutionFunctionName(self): | ||
if self.mode == "LINE": | ||
return "execute_Line" | ||
elif self.mode == "CIRCLE": | ||
return "execute_Circle" | ||
|
||
def execute_Line(self, amount, start, direction): | ||
points = Vector3DList() | ||
for i in range(amount): | ||
points.append(start + i * direction) | ||
return points | ||
|
||
def execute_Circle(self, amount, radius): | ||
points = Vector3DList() | ||
if amount <= 0: return points | ||
factor = 2 * pi / amount | ||
for i in range(amount): | ||
points.append((cos(i * factor) * radius, sin(i * factor) * radius, 0)) | ||
return points | ||
``` | ||
|
||
{{< video generate_points_1.mp4 >}} |
Binary file added
BIN
+122 KB
content/developer_guide/node_development/introduction/copy_location_with_offset_1.mp4
Binary file not shown.
138 changes: 138 additions & 0 deletions
138
content/developer_guide/node_development/introduction/index.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
--- | ||
title : Introduction | ||
weight : 10 | ||
--- | ||
|
||
Welcome to this introduction into node development in Animation Nodes | ||
2.0 or higher. In this guide you will learn how to get started with | ||
writing your own nodes (and I suggest you learn this before you try to | ||
modify existing nodes!). It will consist of multiple parts. Each part | ||
will go a bit more into detail so that you will be able to write more | ||
complex nodes in the end. The following tutorials expect you to have a | ||
basic knowledge in Python. | ||
|
||
Now you are ready to create the actual file. Since Animation Nodes 2.0 | ||
there are two different types of source code files: | ||
|
||
- Python files: `.py` These are just normal python files. Most nodes should be | ||
of this type, there are only a very few nodes which aren't. I suggest to | ||
choose this type always at this stage. It will be easy to change it later if | ||
necessary. | ||
|
||
- [Cython](https://www.cython.org/) files: `.pyx` Cython is another programming | ||
language that builds on top of Python. The main benefit is that it can be | ||
compiled into machine code which can make it much faster than normal python | ||
code. For most nodes this is absolute overkill, the performance benefit will | ||
only be visible be computational expensive operations. Also you can only work | ||
with Cython files when you setup the complete working environment. | ||
|
||
## The First Node | ||
|
||
The first simple node we want to create will be able to copy the | ||
location of one object to another object with an offset. | ||
|
||
First create a file for this node following the rules above. Then copy | ||
this little template code into the file: | ||
|
||
``` python | ||
import bpy | ||
from animation_nodes.base_types import AnimationNode | ||
|
||
class TemplateNode(bpy.types.Node, AnimationNode): | ||
bl_idname = "an_TemplateNode" | ||
bl_label = "Template" | ||
|
||
def create(self): | ||
pass | ||
|
||
def execute(self): | ||
pass | ||
``` | ||
|
||
This template is what I personally use for all new nodes, it is very | ||
easy to build up on it. | ||
|
||
Now we have to choose a name for our new node. The most important thing here is | ||
the `bl_idname` because this is the identifier for your node, when you change | ||
it later on, all files that used this node will be broken. Also it should have | ||
a prefix like `an_` so that it there will be no problems with other add-ons. So | ||
make sure that you give it a good name that tells as exact as possible what the | ||
node does. The class name and the `bl_label` property can be changed later | ||
without problems if necessary. Here is the updated "header" for our specific | ||
example: | ||
|
||
``` python | ||
class CopyLocationWithOffsetNode(bpy.types.Node, AnimationNode): | ||
bl_idname = "an_CopyLocationWithOffsetNode" | ||
bl_label = "Copy Location with Offset" | ||
``` | ||
|
||
The next step is to create the sockets we need. Therefor there is the | ||
`create(self)` function. In our example node we need three inputs. One | ||
source object, one target object and the offset vector. So let's create | ||
those: | ||
|
||
``` python | ||
def create(self): | ||
# Type Name Identifier | ||
self.newInput("Object", "Source", "source") | ||
self.newInput("Object", "Target", "target") | ||
self.newInput("Vector", "Offset", "offset") | ||
``` | ||
|
||
When creating a socket, we have to specify at least three parameters: | ||
|
||
- Type: This will determine which socket will be created, every socket has a | ||
different color. There are a lot of socket types. Here are a few common one: | ||
`Object`, `Vector`, `Float`, `Integer`, `Object List`, ... | ||
|
||
- Name: This name will be displayed in the Node Editor in Blender. | ||
|
||
- Identifier: The identifier is not very important for us yet. However you it | ||
shouldn't change if not absolutely necessary, but changing it is not as bad | ||
as changing the `bl_idname`. It is common to use this identifier as variable | ||
name in the code later. | ||
|
||
Last but not least we have to put some code into the `execute` function. As | ||
soon as the node has input sockets this function has to have parameters. In | ||
this case we need three parameters, the names should correspond to the socket | ||
identifiers. Also the order has to be the same. In the function body we can do | ||
whatever we want with these objects. One thing we have to take care of is an | ||
object can be `None`. This has to be checked before anything else happens | ||
because if there is an error in the node, the whole node tree suddenly stops | ||
working. | ||
|
||
``` python | ||
def execute(self, source, target, offset): | ||
if source is None or target is None: | ||
return | ||
|
||
target.location = source.location + offset | ||
``` | ||
|
||
This node is already fully functional now. Below is all the code for | ||
this node again. | ||
|
||
``` python | ||
import bpy | ||
from animation_nodes.base_types import AnimationNode | ||
|
||
class CopyLocationWithOffsetNode(bpy.types.Node, AnimationNode): | ||
bl_idname = "an_CopyLocationWithOffsetNode" | ||
bl_label = "Copy Location with Offset" | ||
|
||
def create(self): | ||
self.newInput("Object", "Source", "source") | ||
self.newInput("Object", "Target", "target") | ||
self.newInput("Vector", "Offset", "offset") | ||
|
||
def execute(self, source, target, offset): | ||
if source is None or target is None: | ||
return | ||
|
||
target.location = source.location + offset | ||
``` | ||
|
||
{{< video copy_location_with_offset_1.mp4 >}} | ||
|
||
We will continue to work on this node in the next part. |
Binary file added
BIN
+104 KB
content/developer_guide/node_development/outputs/copy_location_with_offset_2.mp4
Binary file not shown.
Binary file added
BIN
+139 KB
content/developer_guide/node_development/outputs/copy_location_with_offset_3.mp4
Binary file not shown.
Oops, something went wrong.