from nodebpy import geometry as gWriting Node Trees
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()
treeThese 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.offsetwith 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")
treewith 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()
)
treewith 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()
treeZones
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 ZoneItem 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"] |
State items are declared through the typed factories on zone.items — one method per data type (zone.items.geometry(), zone.items.float(), zone.items.vector(), …). Each returns a ZoneItem handle whose four role sockets are statically typed to the matching socket class, so editors can autocomplete and type-check the zone body. Pass a linkable as the second argument (or initial=) to link it as the item’s starting value, a plain value to set the socket default, or nothing to declare the item unlinked:
with g.tree() as tree:
zone = g.RepeatZone(10)
random_pos = g.RandomValue.vector(seed=zone.iteration)
geo = zone.items.geometry()
g.JoinGeometry([geo.current, g.Points(10, random_pos)]) >> geo.next
geo.result >> tree.outputs.geometry()
treeThe repeat zone additionally offers datablock item types the simulation zone does not support (zone.items.object(), zone.items.image(), zone.items.collection(), zone.items.material() and zone.items.closure()). The string-typed zone.item(name, initial, type=...) form remains available when the data type is only known at runtime.
The for-each zone has the same style of typed factories for its three item collections — zone.inputs (per-element fields read inside the body), zone.main (per-element results written back onto the input geometry) and zone.generated (values stored on the generated geometry, with a domain= option). The closure zone declares its signature through zone.inputs and zone.outputs, which return the body-side socket directly.
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
treeItem Nodes
Several regular nodes are also driven by dynamic item collections — Capture Attribute, Bake, Field to Grid, Combine/Separate Bundle and Evaluate Closure. They all offer the same typed per-datatype factories as the zones, alongside their items={...} dict constructors:
CaptureAttribute(...).items.vector("Pos", g.Position())returns anItemhandle —item.inputis the field being captured,item.outputthe captured result.Bake().items.geometry("Geo", source)works the same way for bake items.FieldToGrid.float(topology).items.float("Density", field)returns a dual-typedGridItem—item.fieldis the field input socket anditem.gridthe evaluated grid output, each with its own socket class.CombineBundle().items.float("a", 0.5)andSeparateBundle(bundle).items.float("a")declare bundle items and return the typed socket directly (the input to feed, or the output to read).EvaluateClosure(closure).inputs.geometry("Geo", source)and.outputs.vector("Force")declare the closure-call signature; the outputs factory returns the typed result socket.
The bundle and closure factories also accept structure_type= for non-"AUTO" socket shapes.
with g.tree() as tree:
cap = g.CaptureAttribute.face(g.Cube())
pos = cap.items.vector("Pos", g.Position())
(
g.StoreNamedAttribute.face.vector(cap.o.geometry, name="pos", value=pos.output)
>> tree.outputs.geometry()
)
tree