Skip to content

Commit

Permalink
Migrate developer guide.
Browse files Browse the repository at this point in the history
  • Loading branch information
OmarEmaraDev committed Sep 16, 2020
1 parent e28c334 commit 8299cc0
Show file tree
Hide file tree
Showing 13 changed files with 582 additions and 2 deletions.
9 changes: 9 additions & 0 deletions content/developer_guide/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title : Developer Guide
weight : 30
chapter : true
---

# Developer Guide

{{% children %}}
9 changes: 9 additions & 0 deletions content/developer_guide/node_development/_index.md
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 not shown.
152 changes: 152 additions & 0 deletions content/developer_guide/node_development/dynamic_sockets/index.md
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 not shown.
138 changes: 138 additions & 0 deletions content/developer_guide/node_development/introduction/index.md
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 not shown.
Binary file not shown.
Loading

0 comments on commit 8299cc0

Please sign in to comment.