Writing Node Trees

from nodebpy import geometry as g

Adding Nodes

Adding nodes must be done inside of a context. We enter a context using the with keyword. While inside of this context, whenever you call a node class (g.SetPosition()) a node of that type will be added to the current tree.

This first example creates a new tree and adds two new nodes, linking the Set Position node into the Transform Geometry node. The output and input sockets for each are inferred based on simple heuristics around socket type and order.

with g.tree("NewTree") as tree:
    g.SetPosition() >> g.TransformGeometry()

tree

These nodes can be saved as variables for re-use later in the node tree as well. After instantiating a class you can access the input and output sockets through the .i and .o accessors on the class.

These two approaches are equivalent:

with g.tree("AnotherTree") as tree:
    pos = g.SetPosition()

    g.Position() * 0.5 >> pos.i.position
    g.Vector() >> pos.i.offset
with g.tree("AnotherAnotherTree") as tree:
    g.SetPosition(
        offset = g.Vector(),
        position = g.Position() * 0.5
    )

Interface Sockets

The tree’s interface defines what sockets are available as inputs and outputs of the node tree.

We declare them with tree.inputs and tree.outputs — for example tree.inputs.geometry() or tree.outputs.float("Result") — which add the interface socket and return it for linking with other nodes.

with g.tree("NewTree") as tree:
    geom_inputs = [tree.inputs.geometry(f"Geometry_{i}") for i in range(5)]
    g.JoinGeometry(geom_inputs) >> tree.outputs.geometry("The Output Socket")

tree
with g.tree() as tree:
    (
        tree.inputs.integer("Count", 10)
        >> g.Points(position=g.RandomValue.vector(min=(-0.1,-0.1,-0.2)))
        >> tree.outputs.geometry()
    )

tree
with g.tree() as tree:
    count = tree.inputs.integer("Count", 10)
    pos = g.RandomValue.vector() * 0.5 * g.Position()
    g.Points(count, pos) >> tree.outputs.geometry()

tree

Zones

Zones like the repeat and simulation zone are initialized with their SimulationZone() and RepeatZone() constructors. You can add individual RepeatInput() and output nodes, but they require additional setup to be actually linked. The repeat zone can be initialized with a repeat count, which can also be linked to from elsewhere.

We can access the input and output nodes with zone.input and zone.output. The repeat zone has zone.iteration, which is the iteration number of the current zone. The simulation zone has zone.delta_time, which is the time between the previous and current simulation loop.

Because of the complexity of zones, we have the Item helper which gives access to the input & output sockets on the input and output nodes (4 sockets total). For the Simulation and Repeat zones, we have the:

Code Socket
item.initial zone.input.i["Geometry"]
item.current zone.input.o["Geometry"]
item.next zone.output.i["Geometry"]
item.result zone.output.o["Geometry"]
with g.tree() as tree:
    zone = g.RepeatZone(10)
    random_pos = g.RandomValue.vector(seed=zone.iteration)
    geo = zone.item("Geometry", type="GEOMETRY")
    g.JoinGeometry([geo.current, g.Points(10, random_pos)]) >> geo.next
    geo.result >> tree.outputs.geometry()

tree
with g.tree() as tree:
    # this initializes the zone with two socket inputs for each of the values
    # we manually specify the socket names
    zone = g.SimulationZone({"Value": g.Value(), "Vector": g.Vector()})
    zone.input.o["Value"] + 10 >> zone.output

    # this should automatically pick the vector input socket because we are
    # explicit about the VectorMath and it will be the most compatible
    zone.input >> g.VectorMath.add(..., (0.2, 0.4, 0.6)) >> zone.output

tree