Part 3 - Properties

In this chapter we want to give the nodes some properties so that the user of our node can decide which axises he wants to copy from one object to the other. We start with the result of the first part, because we don’t really need output sockets in order to understand this. So here is the original code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import bpy
from ... 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

In order to create properties we first have to import the property types Blender provides: from bpy.props import *.

Note

In general it is a bad idea to do “import star” in Python. However this is the only case where I think this is acceptable because it makes coding much easier and every Blender developer knows what happens (it imports these properties).

Now we want to create three properties, one for each axis. They should have the type bool because we want to turn specific axises on or off.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import bpy
from bpy.props import *
from ... events import propertyChanged
from ... base_types import AnimationNode

class CopyLocationWithOffsetNode(bpy.types.Node, AnimationNode):
    bl_idname = "an_CopyLocationWithOffsetNode"
    bl_label = "Copy Location with Offset"

    useX = BoolProperty(name = "Use X", default = False, update = propertyChanged)
    useY = BoolProperty(name = "Use Y", default = False, update = propertyChanged)
    useZ = BoolProperty(name = "Use Z", default = False, update = propertyChanged)

As you can see I also imported the propertyChanged event handler. This is absolutely necessary but results in a better user experience. This way when the user changes these properties and Property Changed ist activated in the Auto Execution panel an update is triggered. Otherwise Animation Nodes cannot know that these properties changed.

As always the next step is to implement this functionality in the execute method.

1
2
3
4
5
6
7
def execute(self, source, target, offset):
    if source is None or target is None:
        return

    if self.useX: target.location.x = source.location.x + offset.x
    if self.useY: target.location.y = source.location.y + offset.y
    if self.useZ: target.location.z = source.location.z + offset.z

This node is fully function again but there is still one problem. The user can’t see the properties so far. To change that we have to implement another function called draw(self, layout) that will specify which properties will be drawn in the node itself.

1
2
3
4
def draw(self, layout):
    layout.prop(self, "useX")
    layout.prop(self, "useY")
    layout.prop(self, "useZ")

Now the new feature is complete, here is the full code again:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import bpy
from bpy.props import *
from ... events import propertyChanged
from ... base_types import AnimationNode

class CopyLocationWithOffsetNode(bpy.types.Node, AnimationNode):
    bl_idname = "an_CopyLocationWithOffsetNode"
    bl_label = "Copy Location with Offset"

    useX = BoolProperty(name = "Use X", default = False, update = propertyChanged)
    useY = BoolProperty(name = "Use Y", default = False, update = propertyChanged)
    useZ = BoolProperty(name = "Use Z", default = False, update = propertyChanged)

    def create(self):
        self.newInput("Object", "Source", "source")
        self.newInput("Object", "Target", "target")
        self.newInput("Vector", "Offset", "offset")

    def draw(self, layout):
        layout.prop(self, "useX")
        layout.prop(self, "useY")
        layout.prop(self, "useZ")

    def execute(self, source, target, offset):
        if source is None or target is None:
            return

        if self.useX: target.location.x = source.location.x + offset.x
        if self.useY: target.location.y = source.location.y + offset.y
        if self.useZ: target.location.z = source.location.z + offset.z
../../_images/copy_location_with_offset_4.gif