Changelog
v0.18.0 - 2026-05-20
Added
- Pre-commit hooks for
ruffandtychecks and auto-formatting. tytype checking for the fullsrc/directory for type safety- Convenience methods for
ObjectSocketandCollectionSocket:CollectionSocketinstances(transform_space="ORIGINAL", separate_children=False, reset_children=False)— import objects from the collection as instances, returnsGeometrySocket
ObjectSocket:transform(transform_space="ORIGINAL")— get the transform matrix, returnsMatrixSocketlocation(transform_space="ORIGINAL")— get the location, returnsVectorSocketrotation(transform_space="ORIGINAL")— get the rotation, returnsRotationSocketscale(transform_space="ORIGINAL")— get the scale, returnsVectorSocketgeometry(as_instance=False, transform_space="ORIGINAL")— get the geometry, returnsGeometrySocket
- Added
is_selected()method toMenuSwitch- returns theBooleanSocketfor the named menu item that is true when the item is selected
### Fixed
### Changed
v0.17.0 - 2026-05-13
Enhancements
- New methods on
VectorSocketfor applying transforms:rotate(rotation)— apply aRotationSocketviaRotateVector, returnsVectorSockettransform(matrix)— apply aMatrixSocketviaTransformPoint, returnsVectorSocket
- New methods on
RotationSocket:rotate(rotation, rotation_space="GLOBAL")— compose rotations viaRotateRotation, returnsRotationSocketto_euler()— convert to XYZ euler angles, returnsVectorSocket(renamed fromeuler())to_quaternion()— decompose viaRotationToQuaternion, returns aQuaternionnamed tuple with.w,.x,.y,.zto_axis_angle()— decompose viaRotationToAxisAngle, returns anAxisAnglewith.axisand.angle
FloatSocket.mix— factory property for creating typedMixnodes driven by this socket as the factor. Supports.float(),.vector(),.color(),.rotation().FloatSocket.map_range()andVectorSocket.map_range()— remap a socket’s values usingMapRange. Supportsfrom_min,from_max,to_min,to_max,clamp,interpolation_type, andsteps.
normalized = value.map_range(0.0, 100.0, 0.0, 1.0)
remapped_vec = vec.map_range((0,0,0), (1,1,1), (-1,-1,-1), (1,1,1))- New methods on
FloatSocket:clamp(min=0.0, max=1.0)— clamp to range viaClampsqrt()— square rootpower(exponent)— raise to a powerfloor()/ceil()/round()— rounding variantsmodulo(divisor)— floored modulo (always non-negative, consistent with Python%)wrap(min, max)— repeat cyclically within a rangeto_radians()/to_degrees()— angle unit conversion
- New methods on
VectorSocket:cross(other)— cross product, returnsVectorSocketdistance(other)— Euclidean distance, returnsFloatSocketproject(other)— project onto another vectorreflect(normal)— reflect around a normal (normal does not need to be normalised)
- New methods on
IntegerSocket:clamp(min=0, max=1)— clamp to integer rangemodulo(divisor)— integer remainder (always non-negative)
- New method on
MatrixSocket:transform_direction(direction)— apply the matrix to a direction vector, ignoring translation. Use this instead oftransform()for normals and tangents.
- Domain factories on
FloatSocket,VectorSocket,IntegerSocket,BooleanSocket,RotationSocket, andMatrixSocket— select a domain property, then call the operation. Each call returns a single typed socket.- Domain properties:
.point,.edge,.face,.corner,.spline,.instance,.layer - All socket types:
.evaluate()— re-evaluate on the domain viaEvaluateOnDomain;.at(i)— retrieve at an index viaEvaluateAtIndex - Float/Vector additionally:
.min(),.max(),.mean(),.median(),.std_dev(),.variance()(with optionalgroup_index) - Integer additionally:
.min(),.max()(with optionalgroup_index)
- Domain properties:
# Field evaluation — works on all socket types
position.face.evaluate() # VectorSocket — position re-evaluated on face domain
flag.point.at(3) # BooleanSocket — flag value at point index 3
rot.edge.evaluate() # RotationSocket
# Statistics — Float / Vector
lo = position.point.min()
mean = curvature.face.mean()
std_dev = weight.point.std_dev(group_index)fac = g.Value(0.5).o.value
fac.mix.float(0.0, 1.0) # FloatSocket
fac.mix.vector((0,0,0), (1,1,1)) # VectorSocket
fac.mix.color(color_a, color_b) # ColorSocketBreaking Changes
RotationSocket.euler()renamed toRotationSocket.to_euler()for consistency with the newto_quaternion()andto_axis_angle()methods.RotationSocket.w,.x,.y,.zcomponent properties removed. Useto_quaternion()instead.- Multi-output socket methods (
to_quaternion(),to_axis_angle(),find(),svd()) now return typedNamedTupleresults. Both named access and positional unpacking are fully typed.
# Named access
rot.to_quaternion().w # FloatSocket
rot.to_axis_angle().angle # FloatSocket
string.find("/").first_found # IntegerSocket
mat.svd().u # MatrixSocket
# Positional unpacking — all variables are specifically typed
w, x, y, z = rot.to_quaternion()
axis, angle = rot.to_axis_angle()
first, count = string.find("/")
u, s, v = mat.svd()FloatSocket.to_integer(rounding_mode="ROUND")— convert to integer viaFloatToInteger. Accepts"ROUND","FLOOR","CEILING", or"TRUNCATE".
v0.16.0 - 2026-05-05
Enhancements
- Input
VectorSocketnow properly has thex,y,zattributes throughCombineXYZnode. - Socket methods added for strings. Methods added are:
length(),starts_with(),ends_with(),contains(),slice(),format(),replace(),find(),join()
string = g.String("Example String").o.string
string.length() # return g.StringLength().o.length, same as len(string)
string.starts_with() # return g.MatchString().o.result
string.ends_with() # return g.MatchString().o.result
string.contains() # return g.MatchString().o.result
string.slice() # return g.SliceString().o.string
string.format() # return g.FormatString().o.string
string.replace() # return g.ReplaceString().o.string
string.find() # return FindResult(first_found, count)
string.join(x) # return g.JoinStrings(x, delimeter=string)String sockets can also be joined with + operator like python strings.
These two are equivalent.
string + "example"
JoinStrings((string, g.String("example")), separator="").o.stringFloat and integer sockets have to_string() methods:
g.Float().o.value.to_string(3) # specify decimal places
g.Integer().o.integer.to_string()- Math, comparison, and unary operations on sockets now return the output socket of the created node rather than the node itself. This allows method chaining directly on the result.
# Before: result was a Math / VectorMath / Compare node
# After: result is a FloatSocket / VectorSocket / BooleanSocket
pos = g.Position().o.position
scaled = pos * 2.0 # VectorSocket
clamped = (scaled > 0.5) # BooleanSocket
mat = g.CombineTransform() @ g.CombineTransform() # MatrixSocket
vec = g.CombineTransform() @ g.Position() # VectorSocketTo access the underlying builder node from any socket returned by a math operation, use the .builder_node property:
result = g.Value(2.0) ** 3.0 # FloatSocket
result.builder_node # the Math node
result.builder_node.i.value_001 # input socket on that node- Accessing
.oor.ion anyBaseNodenow sets.builder_nodeon the returned socket, pointing back to that node.
pos = g.Position().o.position
pos.builder_node # the Position node- Added
svd()method ontoMatrixSocketwhich returns anSVDResultwith.u,.s,.vproperties. - Add
sign()andnegate()methods onto theFloatSocketfor method chaining. Both returnFloatSocket. - Remove
socket_nameproperty fromBaseSocket, already accessible via.socket.name. - Added
.dot(),.length()and.normalize()methods toVectorSocketwhich create the correspondingDotProduct,VectorLengthandNormalizenodes. - Properties on sockets that aren’t just accessing components of the socket are node methods. They still return sockets and not nodes.
RotationSocket.invert->RotationSocket.invert()RotationSocket.euler->RotationSocket.to_euler()MatrixSocket.invert->MatrixSocket.invert()MatrixSocket.transpose->MatrixSocket.transpose()MatrixSocket.determinant->MatrixSocket.determinant()
Breaking Changes
Compare.switch(false, true)with automatic type inference has been removed. Use the explicit typed factory methods onBooleanSocket.switchinstead.
# Before
(val == 5).switch(g.Cube(), g.IcoSphere())
# After
(val == 5).switch.geometry(g.Cube(), g.IcoSphere())
(val == 5).switch.float(0.0, 1.0)
(val == 5).switch.integer(0, 1)- Math operations no longer return nodes — code that accessed node properties directly on the result (e.g.
result.operation,result.data_type,result.i) must now go throughresult.nodeorresult.builder_node:
result = g.Value(2.0) * 3.0
# Before
result.operation # "MULTIPLY"
result.i.value.default_value # 2.0
# After
result.node.operation # "MULTIPLY"
result.builder_node.i.value.default_value # 2.0Bug Fixes
- The
ColorSocketproperly only indexes to length3inside of the shader asSeparateXYZandCombineXYZdon’t have alpha inputs or outputs. - Fixed bug in mixins that was resulting in node comparison creation when checking if a node / socket was
Noneinstead of usingis Nonecomparison. tree()helper functions ingeometry,shader, andcompositormodules now return a typedTreeBuilder[NodeTreeType]for improved type-checker support.- Fixed
BaseNode._from_node()to correctly wrap an existing node without creating and immediately discarding a temporary node.
v0.15.0 - 2026-04-30
Enhancements
- Boolean sockets have a
switchmethod which creates aSwitchnode with the socket as the input. Allows for quick chaining.
b = tree.inputs.boolean()
b.switch.float(0.1, 0.2)
b.switch.geometry(g.Cube(), g.IcoSphere())- Changes to some node methods to better align with naming inside of Geometry Nodes:
EvaluateAtIndex&EvaluateOnDomain:rotation->quaterniontransform->matrix
SampleIndex&SampelCurve:rotation->quaternion
- Handle adding items for the
ColorRampandFloatCurvenodes to create mappins of0..1floats to values and colors.
Bug Fixes
- The
*Socketclasses have been added to the types for checking. - Inputs and outputs properly listed on the
ForEachGeometryElementnodes. JoinStringlinks in the intended order (by first reversing the iterator before linking which is required for multi-input sockets).StoreNamedAttributehas thedomainanddata_typefactor methods properly exposed forStoreNamedAttribute.face.vector().
v0.14.0 - 2026-04-29
Enhancements
- Nodes which previously took
*argsand**kwargshave been updated to use keyword-only arguments instead. This is a hard breaking change but makes the code more readable and less error-prone. (#69)- Affected nodes:
FieldToGrid,JoinGeometry,MenuSwitch,IndexSwitch,CaptureAttribute,JoinStrings,FormatString,SDFGridBoolean,MeshBoolean,RepeatZone,SimulationZone
- Affected nodes:
- Tree interfaces are defined with
tree.inputs.geometry()methods rather than using the old context-baseds.SocketGeometry(). Both systems have been living side-by-side but this completely removed old system so is a hard breaking change. (#67)
# old system
with tree.inputs:
s.SocketGeometry()
# new system
tree.inputs.geometry()Bug Fixes
v0.13.0 - 2026-04-28
Enhancements
- Support adding of closure nodes (
EvaluateClosureand theClosureInput/ClosureOutputnodes). ConvenienceClosureZoneclass is added similar to the repeat, simulation and for-each-element zones. (#60) - Iteration output for the
RepeatZonehas change.i->.iterationto not confuse with input / output socket access (#60) - Add a
Float()class which just wraps theValue()class / node but is better for hinting towards it’s type and more discoverage ([#58](https://github.com/BradyAJohnston/nodebpy/pull/58))
Bug Fixes
v0.12.0 - 2026-04-25
Enhancements
- Support custom node groups for each node tree via
CustomGeometryGroup,CustomShaderGroup,CustomCompositorGroup(#53)
v0.11.1 - 2026-04-24
Bug Fixes
- Fix type inference for the
>>operator in chains, properly propagating the correct node’s return type.
v0.11.0 — 2026-04-24
Enhancements
- Refactor the mermaid diagram generation. Change
screenshot.py->diagram.pyand added test coverage. - Socket iteration and indexing —
VectorSocket,ColorSocket, andMatrixSocketnow support__getitem__,__iter__, and__len__on both output and input sockets. (#48) Output sockets decompose viaSeparateXYZ/SeparateColor/SeparateMatrix(node reuse on repeated access); input sockets auto-wire aCombineXYZ/CombineColor/CombineMatrixand return the component input socket.
for i, axis in enumerate(g.Position().o.position):
math = axis * float(i)
# Pipe a value into the Y component of a position input
g.Value(5.0) >> g.SetPosition().i.position[1]
mat = g.InstanceTransform().o.transform
vec = g.CombineXYZ(*mat[:3])RotationSocket/MatrixSockethelpers — Added.invertand.transposeproperties onMatrixSocket,.invertonRotationSocket, following the same node-reuse pattern as.x/.y/.z.SocketAccessoroverloads —__getitem__and_getare overloaded so slices returnlist[Socket]and str/int keys returnSocket, eliminating theSocket | list[Socket]union that was blockingenumerateand unpacking.- Blender 5.1 compatibility — Generator updated for Blender 5.1:
FontSockettype,Framenode moved tomanually_defined,SVDclass name normalisation, and classmethod param deduplication fix (min_x/min_y/min_zno longer collapsed tomin). (#50) - Precise operator return types — Arithmetic operators on
FloatSocket→Math,VectorSocket→VectorMath,IntegerSocket→IntegerMath. Comparison operators (<,>,<=,>=,==,!=) →Compare. The>>operator is typed viaTypeVarso the right-hand operand’s exact type is preserved through chains. - Generic factory nodes —
AccumulateField,EvaluateAtIndex,FieldAverage,FieldMinAndMax,EvaluateOnDomain,FieldVariance, andCompareare nowGeneric[_T]. Their_Inputs/_Outputsinner classes carry the type parameter so e.g..point.vector(...)returnsFieldAverage[VectorSocket]and.o.meanresolves toVectorSocket.
Bug Fixes
- Fix doc building and will only deploy on tagged releases. (#49)
- Domain factory pattern — All
_domain_factory/ local-class patterns replaced with proper_DomainFactoryinner classes (includingCaptureAttribute) so the type checker can resolve their return types. SocketAccessoridentifier lookup fix — Added a normalised-identifier pass (normalize_name(id)) so attribute access like.i.value_001correctly resolves Blender identifiers such asValue_001that cannot be round-tripped throughdenormalize_name.
v0.10.2 - 2026-04-21
Enhancements
- Added changelog to the documentation to better track and explain changes in the project.
- Support
len(tree.inputs)andlen(tree.outputs)to get the number of inputs and outputs in the tree. (#43) - Added the GPLv3 license to the project.
v0.10.1 — 2026-04-20
Bug fixes
- Fixed
CaptureAttribute.capture()not correctly linking the captured input socket. (#41)
v0.10.0 — 2026-04-19
The biggest release yet. The headline change is a new typed socket accessor API — node.i.x / node.o.x — that replaces the old node.o_position-style properties and brings full IDE auto-complete and type narrowing to socket access.
Enhancements
node.i / node.o socket accessors (#39)
Sockets are now accessed through .i (inputs) and .o (outputs) accessor objects. Attribute names are the normalised socket identifier, so spaces become underscores and the first letter is lowercased.
node.o.position >> node.i.offset # pipe position into offset
node.o.position.y * 0.2 # operate on the y componentIn node definitions, _Inputs / _Outputs inner classes declare the available sockets and their types so IDEs can provide auto-complete:
class SetPosition(BaseNode):
class _Inputs(SocketAccessor):
geometry: GeometrySocket
position: VectorSocket
offset: VectorSocket
class _Outputs(SocketAccessor):
geometry: GeometrySocketNodeGroupBuilder — custom node groups as Python classes (#31)
Define reusable node groups as plain Python classes. The group tree is built once and cached; subsequent uses insert a Group node pointing at that tree.
class Jitter(NodeGroupBuilder):
_name = "Jitter"
_color_tag = "geometry"
def __init__(self, geometry=None, amount=0.2, seed=0):
super().__init__(Geometry=geometry, Amount=amount, Seed=seed)
@classmethod
def _build_group(cls, tree):
geom = tree.inputs.geometry("Geometry")
amount = tree.inputs.float("Amount", 0.2)
seed = tree.inputs.integer("Seed", 0)
offset = g.RandomValue.vector(min=-1, seed=seed) * amount
_ = g.SetPosition(geom, offset=offset) >> tree.outputs.geometry()
# Composes identically with built-in nodes
g.IcoSphere(subdivisions=4) >> Jitter(amount=0.15) >> outg.tree() module-level helper (#36)
Eliminates the need to import TreeBuilder directly when working with a single editor type.
# Before
from nodebpy import TreeBuilder
with TreeBuilder.geometry("My Group") as tree: ...
# After
from nodebpy import geometry as g
with g.tree("My Group") as tree: ...Simplified interface socket definition (#37)
Interface sockets can now be defined directly on the tree object without a context manager:
with g.tree() as tree:
geo = tree.inputs.geometry("Points")
g.SetPosition(geo)The previous context-manager form still works.
Other changes
- Auto-detection of nodes requiring data-type class methods (e.g.
.float(),.vector()) is now more robust. (#38) builder.pywas split into abuilder/package for maintainability;VectorSocketLinkerwas renamed toVectorSocket. (#35)- Internal type aliases cleaned up —
InputFloatreplacesTYPE_INPUT_VALUEetc. (#34)
v0.9.1 — 2026-03-27
Enhancements
== / != comparison operators (#28)
BaseNode objects now support Python equality operators, returning a Compare node. Chain .switch() to immediately branch on the result:
# Creates a Compare node then routes into a Switch
(g.Value(5.0) > 2.0).switch(false=g.Cube(), true=g.IcoSphere())Data-type-specific socket linkers (#29)
VectorSocket, ColorSocket, FloatSocket, and IntegerSocket carry type-specific operations (e.g. .x, .y, .z on vectors) so arithmetic stays typed all the way through a chain.
Other changes
- Documentation styling improvements. (#27)
v0.8.0 — 2026-03-16
Enhancements
networkx is now an optional dependency (#24)
nodebpy now has no hard dependencies outside of bpy, making it easier to vendor into add-ons. A built-in simple arranger is used when networkx is absent; the Sugiyama layout remains the default when it is installed.
v0.7.2 — 2026-03-15
Enhancements
matrix @ vector creates a TransformPoint node (#22)
matrix @ g.Position() # → TransformPoint nodeBug fixes
- Fixed
...(ellipsis) handling in>>chains — type-aware output selection now works correctly when skipping intermediate nodes. (#23)
v0.7.1 — 2026-03-14
Enhancements
Color >> Shader linking (#21)
Piping a color socket into a shader input is now handled automatically, matching the way Blender promotes color connections in the node editor.
v0.7.0 — 2026-03-13
Enhancements
Panels for tree interfaces (#20)
Group interface sockets can be organised into named panels:
with tree.inputs.panel("Settings"):
s.SocketFloat("Amount", 0.2)
s.SocketInt("Seed", 0)Integer math is now handled correctly in Shader and Compositor editors (mapped to float math, as Blender does not expose integer math there).
v0.6.0 — 2026-03-13
Bug fixes
- Fixed
MenuSwitchnode creation and socket wiring. (#18)
Other changes
- Mermaid diagram generation improvements: math node operators are now shown, and socket connections use
->instead of>>. (#19)
v0.5.0 — 2026-03-13
Enhancements
Compositor and Material (Shader) node editors (#12)
nodebpy now supports building Compositor and Shader/Material node trees in addition to Geometry Nodes.
Remaining Python math operators (#15, #16)
** (power), % (modulo), // (floor divide), abs(), and unary - are all wired up. Operator order for vector math was also corrected.