Skip to content
Punya Aachman edited this page Dec 31, 2019 · 5 revisions

Creating a Node in Preset Category

Let's create a node that operates on "Object" mode and displaces the selected (active) object in the viewport by +10 units in Z axis (upwards).

Creating the file

  1. Navigate to <Sorcar dir>/nodes/<category>

    .../Sorcar/nodes/object_operators in this case.

File Structure

  1. Create a new file Sc<ShortName>.py

    Let us name the file "ScDisplaceUp.py". This name must be unique. The display name (called label) can be changed later in the code, for example "Displace Upwards (10 units)".

Creating a fundamental node class

  1. Open the created file and import Blender's python package
    import bpy
  2. Import Blender's node base class and Sorcar's base classes
    from bpy.types import Node
    from .._base.node_base import ScNode
    from .._base.node_<category> import Sc<Category>Node

    Sorcar's base classes include methods to execute the node and manage input & output socket values. They also create a suitable environment (correct object selection, mode, etc.) before executing. For more information on base categories, look at .../Sorcar/nodes/_base/ directory.

  3. Create a class (having the same name as the file) which inherits from Node and Sc<Category>Node
    class Sc<ShortName>(Node, Sc<Category>Node):
  4. Set attributes bl_idname and bl_label
    bl_idname = "Sc<ShortName>"
    bl_label = "<Small Description>"
import bpy

from bpy.types import Node
from .._base.node_base import ScNode
from .._base.node_operator import ScObjectOperatorNode

class ScDisplaceUp(Node, ScObjectOperatorNode):
    bl_idname = "ScDisplaceUp"
    bl_label = "Displace Upwards (10 units)"

"Add Node" menu Basic structure of node

Adding input & output sockets

Let us add two input sockets of different types and one output socket. For more information on available socket types in sorcar, see .../Sorcar/sockets/ directory.

  1. Override function init(self)
    def init(self):
  2. Call init() method of the parent class before implementing custom sockets
    super().init()

Mandatory-Link Input Socket (value derived from linked socket)

Let's add a boolean input socket with name "Multiplier" which we will use later to check if the displacement should be multiplied by a factor.

  1. Add a new input socket of a specific type with a unique display name
    self.inputs.new("ScNodeSocket<Type>", "<Display Name>")

    https://docs.blender.org/api/current/bpy.types.NodeInputs.html#bpy.types.NodeInputs.new

Link-only input socket

Optional-Link Input Socket (value can be set in the node itself)

Let's add a float (number) input socket with name "Factor" which we will use later to multiply the default displacement value (i.e. 10).

  1. Import Blender UI property
    from bpy.props import <Type>Property

    https://docs.blender.org/api/current/bpy.props.html

  2. Create a property inside the class with prefix in_
    in_<name>: <Type>Property(..., ..., update=ScNode.update_value)

  3. Add a new input socket referencing the created property
    self.inputs.new("ScNodeSocket<Type>", "<Display Name>").init("in_<name>")

    By default, the property remains hidden in the node. It can be accessed by the N-Panel in the Node Editor. To set the input socket to visible by default, override the argument of init() method:
    self.inputs.new("ScNodeSocket<Type>", "<Display Name>").init("in_<name>", True)

UI input socket

Output Socket (value set after node execution)

Let's add a float (number) output socket with name "Factor" which will store the actual value of displacement (10 x Factor).

  1. Add a new output socket of a specific type with a unique display name (input & output sockets can have the same name)
    self.outputs.new("ScNodeSocket<Type>", "<Display Name>")

    https://docs.blender.org/api/current/bpy.types.NodeOutputs.html#bpy.types.NodeOutputs.new

Output socket

    in_factor: FloatProperty(default=2.0, min=0.0, update=ScNode.update_value)

    def init(self, context):
        super().init(context)
        self.inputs.new("ScNodeSocketBool", "Multiplier")
        self.inputs.new("ScNodeSocketNumber", "Factor").init("in_factor", True)
        self.outputs.new("ScNodeSocketNumber", "Factor")

Configuring error conditions

Once the node structure is implemented, we need to make sure that each input value is correct (e.g. Enum) or in the required range (e.g. Number).

  1. Override function error_conditions(self)
    def error_conditions(self):
  2. Return the boolean value ensuring that error_condition() method of the parent class is among the conditions checked
    return (super().error_condition() or <condition> or <condition>)
    def error_condition(self):
        return (
            super().error_condition()
            or (self.inputs["Factor"].default_value < 0.0)
        )

Setting pre-execute environment (Optional)

Sorcar's base category classes automatically create the required environment for the node to execute in. However we can further change or modify the scene (say delete all objects or recenter the selected object) as per the requirements.

  1. Override function pre_execute(self)
    def pre_execute(self):
  2. Optionally, call pre_execute() method of the parent class (order of the call matters)
    super().pre_execute()

Main functionality of the node

Once the environment is set, we can modify the scene or object in the intended way. We can use the input socket values to drive the function arguments.

  1. Override method functionality(self)
    def functionality(self):
  2. Access the input values
    self.inputs["<Display Name>"].default_value
  3. Write the functionality of the node using input values as required

In this function, we will access the "Multiplier" boolean input value and check if we need to multiply the default value (i.e 10) by the "Factor" input value. If not, then we will set the 3rd index (Z) of "location" attribute of the input object (socket provided by base class).

    def functionality(self):
        if (self.inputs["Multiplier"].default_value):
            self.inputs["Object"].default_value.location[2] += 10 * self.inputs["Factor"].default_value
        else:
            self.inputs["Object"].default_value.location[2] += 10

Setting output socket values (post-execute)

After the main functionality of node gets executed, we need to set the custom output node values manually. Sockets provided by the base category classes are not required to be set, but can be overridden if required.

  1. Override method post_execute(self)
    def post_execute(self):
  2. Get the results from parent class
    out = super().post_execute()
  3. Add values for corresponding output sockets in the obtained dictionary
    out["<Display Name>"] = <value>
    out["<Display Name>"] = <value>
    out["<Display Name>"] = <value>
  4. Return the dictionary
    return out

Since we only have one custom output socket, we will access the "Multiplier" boolean input value again, check if we need to multiply the default value (i.e 10) by the "Factor" input value and set it to the output socket for further use.

    def post_execute(self):
        out = super().post_execute()
        if (self.inputs["Multiplier"].default_value):
            out["Factor"] = 10 * self.inputs["Factor"].default_value
        else:
            out["Factor"] = 10
        return out

Testing the node

Custom node in action

File:

ScDisplaceUp.py

Things to remember

  • Variables declared outside any function (in the class body) without using Blender's property types are immutable.
  • Certain data-types use common sockets to transfer information, such as:
    • Int, Float, Angle --> Number socket
    • String, Enum --> String socket
    • Array (of size 3), Vector --> Vector socket
  • Since the whole node network is re-evaluated on any value change, it is crucial to optimise as much as possible to reduce processing time (higher FPS)
  • Use bpy.ops inbuilt functions wherever possible rather than writing your custom implementation.
  • These functions are provided for the convenience of the developer. You can create your own functions as well. Make sure that the flow of execution doesn't get affected:
    ...<-[output<-node<-input]<-[output<-node<-input]<-[output<-node<-input]<-...

Getting started

Tutorials

Examples


Nodes

Component Operators
Constants
Deletion
Flow Control
Inputs
Modifiers
Object Operators
Selection
Settings
Transform
Utilities

Sockets


Clone this wiki locally