from nodebpy import TreeBuilder, geometry as g, sockets as sWhat happens when we add nodes?
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 TreeBuilder("NewTree") as tree:
g.SetPosition() >> g.TransformGeometry()
treegraph LR
N0("SetPosition"):::geometry-node
N1("Transform"):::geometry-node
N0 -->|"Geometry->Geometry"| N1
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
These nodes can be saved as variables for re-use later in the node tree as well. After instantiating a class you can specify the input and output sockets using the i_* and o_* properties on the class.
These two approaches are equivalent:
with TreeBuilder("AnotherTree") as tree:
pos = g.SetPosition()
g.Position() * 0.5 >> pos.i_position
g.Vector() >> pos.i_offsetwith TreeBuilder("AnotherAnotherTree") as tree:
g.SetPosition(
offset = g.Vector(),
position = g.Position() * 0.5
)graph LR
N0("InputPosition"):::input-node
N1("VectorMath<br/><small>(SCALE)</small><br/><small>×0.5</small>"):::vector-node
N2("InputVector"):::input-node
N3("SetPosition"):::geometry-node
N0 -->|"Position->Vector"| N1
N1 -->|"Vector->Position"| N3
N2 -->|"Vector->Offset"| N3
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
Node Input Sockets
The socket interface nodes define what values / sockets are available as inputs for the node tree.
We define them in a similar way to the socekts themselves, using context with the tree.inputs and tree.outputs and adding sockets with the s.SocketGeometry().
with TreeBuilder("NewTree") as tree:
with tree.inputs:
geom_inputs = [s.SocketGeometry(f"Geometry_{i}") for i in range(5)]
with tree.outputs:
g.JoinGeometry(*geom_inputs) >> s.SocketGeometry("The Output Socket")
treegraph LR
N0("NodeGroupInput"):::default-node
N1("JoinGeometry"):::geometry-node
N2("NodeGroupOutput"):::default-node
N0 -->|"Geometry_4->Geometry"| N1
N0 -->|"Geometry_3->Geometry"| N1
N0 -->|"Geometry_2->Geometry"| N1
N0 -->|"Geometry_1->Geometry"| N1
N0 -->|"Geometry_0->Geometry"| N1
N1 -->|"Geometry->The Output Socket"| N2
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
with TreeBuilder() as tree:
with tree.inputs:
count = s.SocketInt("Count", 10)
with tree.outputs:
output = s.SocketGeometry()
(
count
>> g.Points(position=g.RandomValue.vector(min=(-0.1,-0.1,-0.2)))
>> output
)
treegraph LR
N0("NodeGroupInput"):::default-node
N1("RandomValue<br/><small>(-0.1,-0.1,-0.2)</small>"):::converter-node
N2("Points"):::geometry-node
N3("NodeGroupOutput"):::default-node
N1 -->|"Value->Position"| N2
N0 -->|"Count->Count"| N2
N2 -->|"Points->Geometry"| N3
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
with TreeBuilder() as tree:
with tree.inputs:
count = s.SocketInt("Count", 10)
with tree.outputs:
output = s.SocketGeometry()
(
count
>> g.Points(position=g.RandomValue.vector() * 0.5 * g.Position())
>> output
)
treegraph LR
N0("NodeGroupInput"):::default-node
N1("RandomValue"):::converter-node
N2("VectorMath<br/><small>(SCALE)</small><br/><small>×0.5</small>"):::vector-node
N3("InputPosition"):::input-node
N4("VectorMath<br/><small>(MULTIPLY)</small>"):::vector-node
N5("Points"):::geometry-node
N6("NodeGroupOutput"):::default-node
N1 -->|"Value->Vector"| N2
N2 -->|"Vector->Vector"| N4
N3 -->|"Position->Vector"| N4
N4 -->|"Vector->Position"| N5
N0 -->|"Count->Count"| N5
N5 -->|"Points->Geometry"| N6
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
Zones
Zones like the repeat and simulation zone are initialized with their SimulationZone() and RepeatZone() constructors. You can add individvual RepeatInput() node and output, 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 as the zone.i which is the iteration number of the current zone. Simulation zone has the zone.output.o_delta_time which is the time between previous and current simulation loop.
Both input and output nodes can automatically detect and capture links when you attempt to link into them with >>. The zone.input.capture() method also allows you to explicitly capture a link or a value, returning the output socket for further linking.
with TreeBuilder(arrange=None) as tree:
zone = g.RepeatZone(10)
join = g.JoinGeometry()
# a geometry socket is added to the zone when we try to connect from the Join Geometry
# to the zone output, which is then available for the zone.input >> join
join >> zone.output >> g.SetPosition()
zone.input >> join
g.Points(zone.i, position=g.RandomValue.vector(seed=zone.i)) >> join
treegraph LR
N0("RepeatInput"):::default-node
N1("RepeatOutput"):::default-node
N2("JoinGeometry"):::geometry-node
N3("SetPosition"):::geometry-node
N4("RandomValue"):::converter-node
N5("Points"):::geometry-node
N2 -->|"Geometry->Geometry"| N1
N1 -->|"Geometry->Geometry"| N3
N0 -->|"Geometry->Geometry"| N2
N0 -->|"Iteration->Seed"| N4
N0 -->|"Iteration->Count"| N5
N4 -->|"Value->Position"| N5
N5 -->|"Points->Geometry"| N2
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px
with TreeBuilder(arrange=None) as tree:
# this initializes the zone with two socket inputs for each of the values
zone = g.SimulationZone(g.Value(), g.Vector())
# this explicitly grabs the "Value" socket (which got it's name from the g.Value() node)
# and adds 10 then attempts to plug it into the zone output (it will choose the float
# socket instead of the vector socket because that is the most compatible)
zone.input.outputs["Value"] + 10 >> zone.output
# this should automatically pick the vector input socket because we are
# explicity about the VectorMath and it will be the most compatible
zone.input >> g.VectorMath.add((1.2, 1.2, 1.2)) >> zone.output
treegraph LR
N0("Value"):::input-node
N1("InputVector"):::input-node
N2("SimulationInput"):::default-node
N3("SimulationOutput"):::default-node
N4("Math<br/><small>(ADD)</small>"):::converter-node
N5("VectorMath<br/><small>(ADD)</small>"):::vector-node
N0 -->|"Value->Value"| N2
N1 -->|"Vector->Vector"| N2
N2 -->|"Value->Value"| N4
N4 -->|"Value->Value"| N3
N2 -->|"Vector->Vector"| N5
N5 -->|"Vector->Vector"| N3
classDef geometry-node fill:#e8f5f1,stroke:#3a7c49,stroke-width:2px
classDef converter-node fill:#e6f1f7,stroke:#246283,stroke-width:2px
classDef vector-node fill:#e9e9f5,stroke:#3C3C83,stroke-width:2px
classDef texture-node fill:#fef3e6,stroke:#E66800,stroke-width:2px
classDef shader-node fill:#fef0eb,stroke:#e67c52,stroke-width:2px
classDef input-node fill:#f1f8ed,stroke:#7fb069,stroke-width:2px
classDef output-node fill:#faf0ed,stroke:#c97659,stroke-width:2px
classDef default-node fill:#f0f0f0,stroke:#5a5a5a,stroke-width:2px