Changelog

v520.2.0 - 2026-06-16

Enhancements

  • Asset node-group APIs — generate typed nodebpy classes for node-group assets, so an asset reads, links and type-checks like any other node. Unlike a Custom*Group (which builds its tree), an asset class appends the asset’s node group from a .blend at runtime and points a Group node at it.
    • Blender’s bundled essentials are generated into nodebpy.nodes.{geometry,shader,compositor} and exported alongside the built-in nodes, so they’re used exactly like any other node:
    from nodebpy import geometry as g
    
    mesh = g.SmoothByAngle(mesh=g.Cube(), angle=0.6).o.mesh  # an asset, fully typed
    g.Array(geometry=mesh, count=4)
    • nodebpy.assets.generate_asset_api(library, output_path) generates the same typed classes for your own assets — point it at a .blend shipped in your package via PackageLibrary(__file__, "…/assets.blend") (or BundledLibrary("…") for a Blender-bundled library) and import the result like any other node module.
    • New runtime bases AssetGeometryGroup / AssetShaderGroup / AssetCompositorGroup (parallel to the Custom*Group builders) back these classes; library resolution is handled by BundledLibrary / PackageLibrary.

Fixes

  • Fixed a bug where AxesToRotation silently did not set the primary and secondary when instantiating a new node

v520.1.1 - 2026-06-15

Internal

  • Code generator refactor — the generator gained a register_customization registry (mirroring codegen.register_emitter), so node classes that previously had to be hand-written in full inside manual.py are now auto-generated, with small reusable mixins or bespoke __init__/factory bodies layered on at generation time. The Bézier handle nodes, Switch, the bundle pack/unpack nodes, the items nodes (Bake, FieldToList, FormatString), and the field-evaluation nodes (AccumulateField, EvaluateAtIndex, FieldAverage, FieldMinAndMax, EvaluateOnDomain, FieldVariance) were moved off the hand-written path. No public API changes.
  • Generator reorganised into a gen/ package — the monolithic generate.py was split into focused modules (config, customizations, model, introspect, emit, writers), kept outside src/ so it never ships in the wheel. Run with python -m gen (or python -m gen --only geometry to regenerate a single tree). The skip / hand-written / generate decision is now a single Disposition derived from the same class-name logic used for generation, introspection is cached so each node is only inspected once, and the generator loads the dependency-free types leaf standalone so it can run even when the generated tree is mid-refactor.
  • The code generator now infers generic typing more completely — generic input sockets that track a node’s data type, and nodes whose output type is fixed while the inputs vary — tightening the type hints on HashValue, ListLength, ValueToString, FilterList, StoreBundleItem, SetSelection, and StoreNamedGrid.

Fixes

  • CombineBundle / SeparateBundle item construction is now part of the generated output, fixing a latent issue where their custom constructors were silently overwritten whenever the node classes were regenerated.

v520.1.0 - 2026-06-15

Enhancements

  • Nodes to code (to_python)TreeBuilder.to_python() (and the standalone nodebpy.export.to_python()) converts any node tree back into idiomatic nodebpy Python — interface sockets, properties, links, zones, frames and nested groups included. It recognises lifted operators (Math*, SeparateXYZ.x), socket methods, factory methods and zone item APIs, so generated code reads like hand-written nodebpy. Validated end-to-end against Blender’s full bundled geometry, shader and compositor essentials asset libraries, so it round-trips real-world trees, not only ones built with nodebpy. See Nodes to Code. Options:
    • snapshot_positions=True — capture and restore each node’s authored location (top-level and inside nested groups) instead of auto-laying-out the rebuilt tree.
    • keep_reroutes=True — preserve reroute nodes as g.Reroute(...) pass-throughs instead of collapsing each reroute chain into a direct link; pairs with snapshot_positions to reproduce the original wire routing.
    • top_level="class" — emit every node group, including the working tree, as a Custom*Group subclass, for archiving a set of groups as plain reusable Python. Defaults to the with TreeBuilder(...) as tree: form.
    • Nested frames are reconstructed as nested with g.Frame(): blocks (including container frames that hold only sub-frames).
    • format=True (default) runs the output through ruff format when the optional ruff package is installed (pip install nodebpy[format]), for tidier source; a no-op when ruff is unavailable.
    • strict=False emits a # TODO placeholder for unsupported nodes; register_emitter(bl_idname) plugs in a custom generator for any node type.
  • NodeGroupBuilder.create_group() — classmethod that builds and returns a custom group’s node tree without an active TreeBuilder context (it opens its own), reusing an existing tree of the same name. Lets a group be pre-built and assigned directly to a node’s node_tree.
  • TreeBuilder.node_positions — a read/write {node name: (x, y)} mapping for snapshotting and restoring node locations, plus TreeBuilder.disable_arrange() to skip the auto-layout that otherwise runs on context exit.
  • Bundle and closure item APIsCombineBundle(items={name: source}) / SeparateBundle(bundle, items={name: "TYPE"}), EvaluateClosure(closure, input_items=..., output_items=...), and a ClosureZone wrapper (cz.input_item(...), cz.output_item(...), cz.closure) for defining a closure’s body inline.
  • Colour field evaluationColorSocket gained the domain field-evaluation methods (.point.at(i), .point.evaluate(), …) via EvaluateAtIndex / EvaluateOnDomain, matching the other socket types.
  • Socket methods for AlignRotationToVector for VectorSocket and RotationSocket (align_rotation() and align_to_vector()
  • Grid socket operator methods — chainable methods on the *SocketGrid types that build and wire up the matching grid node, so grid pipelines can be expressed fluently:
    • All grids — sample(position, interpolation), sample_index(x, y, z), field_to_grid(), clip(...), dilate_erode(steps, connectivity, tiles), prune(threshold, mode), voxelize(), to_points()
    • Float, vector and integer grids — mean(width, iterations), median(width, iterations)
    • Float grids — gradient(), laplacian(), sdf_fillet(), sdf_laplacian(), sdf_mean(), sdf_mean_curvature(), sdf_median(), sdf_offset(), to_mesh()
    • Vector grids — curl(), divergence()
grid = g.CubeGridTopology() >> g.FieldToGrid.boolean()
density = grid.capture_float(g.NoiseTexture().o.fac)
flow = density.dilate_erode(1).laplacian().gradient().divergence()

Fixes

  • Grid mean() and median() now pass the correct data_type (Blender’s VALUE float type is mapped to FLOAT), fixing a crash when calling them on float grids
  • INT_VECTOR sockets (e.g. compositor Image Info “Dimensions”) are now link-compatible with regular VECTOR sockets, matching Blender’s implicit conversion.
  • Multi-input sockets (JoinBundle, …) accept an iterable of sources via their constructor, linking each in turn (as JoinGeometry already did).
  • Linking a node into a Reroute (or any other adaptive __extend__ socket, such as Viewer) now works from any source type — the reroute adapts instead of rejecting the connection.

Breaking Changes

  • g.SetHandleType() now defaults to left=True, right=True (mode = {'LEFT', 'RIGHT'}), matching Blender’s native default for a freshly added node. Previously it defaulted to an empty mode, which set no handle types. The shared left/right/mode logic for SetHandleType and HandleTypeSelection was factored into a mixin; SetHandleType also gained a mode property for parity.

v520.0.1 - 2026-06-05

Fixes

  • Import and usage of the arrange() function properly handles the optional netowrkx dependency

v520.0.0 - 2026-06-04

Enhancements

  • Added leading(), trailling() and total() methods from AccumulateField node onto relevant sockets. Added to Float, Vector, Integer and Matrix sockets.
  • Blender 5.2 support — generated nodes updated to include the nodes for Blender 5.2.
  • List socket subtypes — new *SocketList socket types matching Blender 5.2’s list sockets, added for every base socket type (FloatSocketList, IntegerSocketList, VectorSocketList, ColorSocketList, BooleanSocketList, RotationSocketList, MatrixSocketList, StringSocketList, MenuSocketList, GeometrySocketList, ObjectSocketList, MaterialSocketList, CollectionSocketList, ImageSocketList, and more). List sockets carry methods for working with the list:
    • list_length() — number of elements, also available via len()
    • get(index) — retrieve an element (or a sub-list when indexed with an IntegerSocketList) via GetListItem
    • filter(selection) — keep elements where selection is true
    • sort(sort_weight, group_id=None, selection=None) — sort via SortList
    • reverse() — reverse the list
    • list_slice(start, stop, step) — Python-style slicing, also driven through [] indexing and slicing (e.g. list[::2], list[-3:-1])
indices = g.Index().o.index.to_list(10) # an IntegerSocketList of equal to `range(10)`
evens = indices[::2]          # IntegerSocketList via GetListItem
count = len(indices)          # IntegerSocket via ListLength
first = indices.get(0)        # IntegerSocket
  • Grid socket subtypes — new *SocketGrid socket types for volume grids (FloatSocketGrid, IntegerSocketGrid, VectorSocketGrid, BooleanSocketGrid), with transform(), background_value(), and component indexing.
  • to_list(count) — convert a field socket into a list socket via FieldToList. Available on FloatSocket, IntegerSocket, VectorSocket, ColorSocket, BooleanSocket, RotationSocket, MatrixSocket, StringSocket, and MenuSocket.
  • New StringSocket methodsuppercase() and lowercase() (via SetStringCase) and reverse() (via ReverseString).
string = g.String("Example").o.string
string.uppercase()   # StringSocket via SetStringCase
string.lowercase()   # StringSocket via SetStringCase
string.reverse()     # StringSocket via ReverseString

Fixed

Breaking Changes

v0.18.0 - 2026-05-20

Added

  • Pre-commit hooks for ruff and ty checks and auto-formatting.
  • ty type checking for the full src/ directory for type safety
  • Convenience methods for ObjectSocket and CollectionSocket:
    • CollectionSocket
      • instances(transform_space="ORIGINAL", separate_children=False, reset_children=False) — import objects from the collection as instances, returns GeometrySocket
    • ObjectSocket:
      • transform(transform_space="ORIGINAL") — get the transform matrix, returns MatrixSocket
      • location(transform_space="ORIGINAL") — get the location, returns VectorSocket
      • rotation(transform_space="ORIGINAL") — get the rotation, returns RotationSocket
      • scale(transform_space="ORIGINAL") — get the scale, returns VectorSocket
      • geometry(as_instance=False, transform_space="ORIGINAL") — get the geometry, returns GeometrySocket
  • Added is_selected() method to MenuSwitch - returns the BooleanSocket for the named menu item that is true when the item is selected

v0.17.0 - 2026-05-13

Enhancements

  • New methods on VectorSocket for applying transforms:
    • rotate(rotation) — apply a RotationSocket via RotateVector, returns VectorSocket
    • transform(matrix) — apply a MatrixSocket via TransformPoint, returns VectorSocket
  • New methods on RotationSocket:
    • rotate(rotation, rotation_space="GLOBAL") — compose rotations via RotateRotation, returns RotationSocket
    • to_euler() — convert to XYZ euler angles, returns VectorSocket (renamed from euler())
    • to_quaternion() — decompose via RotationToQuaternion, returns a Quaternion named tuple with .w, .x, .y, .z
    • to_axis_angle() — decompose via RotationToAxisAngle, returns an AxisAngle with .axis and .angle
  • FloatSocket.mix — factory property for creating typed Mix nodes driven by this socket as the factor. Supports .float(), .vector(), .color(), .rotation().
  • FloatSocket.map_range() and VectorSocket.map_range() — remap a socket’s values using MapRange. Supports from_min, from_max, to_min, to_max, clamp, interpolation_type, and steps.
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 via Clamp
    • sqrt() — square root
    • power(exponent) — raise to a power
    • floor() / ceil() / round() — rounding variants
    • modulo(divisor) — floored modulo (always non-negative, consistent with Python %)
    • wrap(min, max) — repeat cyclically within a range
    • to_radians() / to_degrees() — angle unit conversion
  • New methods on VectorSocket:
    • cross(other) — cross product, returns VectorSocket
    • distance(other) — Euclidean distance, returns FloatSocket
    • project(other) — project onto another vector
    • reflect(normal) — reflect around a normal (normal does not need to be normalised)
  • New methods on IntegerSocket:
    • clamp(min=0, max=1) — clamp to integer range
    • modulo(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 of transform() for normals and tangents.
  • Domain factories on FloatSocket, VectorSocket, IntegerSocket, BooleanSocket, RotationSocket, and MatrixSocket — 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 via EvaluateOnDomain; .at(i) — retrieve at an index via EvaluateAtIndex
    • Float/Vector additionally: .min(), .max(), .mean(), .median(), .std_dev(), .variance() (with optional group_index)
    • Integer additionally: .min(), .max() (with optional group_index)
# 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)  # ColorSocket

Breaking Changes

  • RotationSocket.euler() renamed to RotationSocket.to_euler() for consistency with the new to_quaternion() and to_axis_angle() methods.
  • RotationSocket.w, .x, .y, .z component properties removed. Use to_quaternion() instead.
  • Multi-output socket methods (to_quaternion(), to_axis_angle(), find(), svd()) now return typed NamedTuple results. 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 via FloatToInteger. Accepts "ROUND", "FLOOR", "CEILING", or "TRUNCATE".

v0.16.0 - 2026-05-05

Enhancements

  • Input VectorSocket now properly has the x, y, z attributes through CombineXYZ node.
  • 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.string

Float 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()           # VectorSocket

To 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 .o or .i on any BaseNode now sets .builder_node on the returned socket, pointing back to that node.
pos = g.Position().o.position
pos.builder_node   # the Position node
  • Added svd() method onto MatrixSocket which returns an SVDResult with .u, .s, .v properties.
  • Add sign() and negate() methods onto the FloatSocket for method chaining. Both return FloatSocket.
  • Remove socket_name property from BaseSocket, already accessible via .socket.name.
  • Added .dot(), .length() and .normalize() methods to VectorSocket which create the corresponding DotProduct, VectorLength and Normalize nodes.
  • 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 on BooleanSocket.switch instead.
# 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 through result.node or result.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.0

Bug Fixes

  • The ColorSocket properly only indexes to length 3 inside of the shader as SeparateXYZ and CombineXYZ don’t have alpha inputs or outputs.
  • Fixed bug in mixins that was resulting in node comparison creation when checking if a node / socket was None instead of using is None comparison.
  • tree() helper functions in geometry, shader, and compositor modules now return a typed TreeBuilder[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 switch method which creates a Switch node 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 -> quaternion
      • transform -> matrix
    • SampleIndex & SampelCurve:
      • rotation -> quaternion
  • Handle adding items for the ColorRamp and FloatCurve nodes to create mappins of 0..1 floats to values and colors.

Bug Fixes

  • The *Socket classes have been added to the types for checking.
  • Inputs and outputs properly listed on the ForEachGeometryElement nodes.
  • JoinString links in the intended order (by first reversing the iterator before linking which is required for multi-input sockets).
  • StoreNamedAttribute has the domain and data_type factor methods properly exposed for StoreNamedAttribute.face.vector().

v0.14.0 - 2026-04-29

Enhancements

  • Nodes which previously took *args and **kwargs have 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
  • Tree interfaces are defined with tree.inputs.geometry() methods rather than using the old context-based s.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()
  • Drop the .inputs and .outputs accessors for the nodes, in favor of just using .i and .o properties directly. This will reduce confusion as .inputs and .outputs are available on the base bpy nodes themselves. (#65)
  • Better type hinting and stubs for the MenuSwitch and IndexSwitch nodes. (#62)

Bug Fixes

v0.13.0 - 2026-04-28

Enhancements

  • Support adding of closure nodes (EvaluateClosure and the ClosureInput / ClosureOutput nodes). Convenience ClosureZone class is added similar to the repeat, simulation and for-each-element zones. (#60)
  • Iteration output for the RepeatZone has change .i -> .iteration to not confuse with input / output socket access (#60)
  • Add a Float() class which just wraps the Value() class / node but is better for hinting towards it’s type and more discoverage ([#58](https://github.com/BradyAJohnston/nodebpy/pull/58))

Bug Fixes

  • Properly expose mode, domain and data_type as methods and method factories for SampleIndex and SampleCurve. (#61)
  • Math on a ColorSocket now uses the VectorMath instead of Math preserving the RGB channels as XYZ. (#56)

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.py and added test coverage.
  • Socket iteration and indexingVectorSocket, ColorSocket, and MatrixSocket now support __getitem__, __iter__, and __len__ on both output and input sockets. (#48) Output sockets decompose via SeparateXYZ/SeparateColor/SeparateMatrix (node reuse on repeated access); input sockets auto-wire a CombineXYZ/CombineColor/CombineMatrix and 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/MatrixSocket helpers — Added .invert and .transpose properties on MatrixSocket, .invert on RotationSocket, following the same node-reuse pattern as .x/.y/.z.
  • SocketAccessor overloads__getitem__ and _get are overloaded so slices return list[Socket] and str/int keys return Socket, eliminating the Socket | list[Socket] union that was blocking enumerate and unpacking.
  • Blender 5.1 compatibility — Generator updated for Blender 5.1: FontSocket type, Frame node moved to manually_defined, SVD class name normalisation, and classmethod param deduplication fix (min_x/min_y/min_z no longer collapsed to min). (#50)
  • Precise operator return types — Arithmetic operators on FloatSocketMath, VectorSocketVectorMath, IntegerSocketIntegerMath. Comparison operators (<, >, <=, >=, ==, !=) → Compare. The >> operator is typed via TypeVar so the right-hand operand’s exact type is preserved through chains.
  • Generic factory nodesAccumulateField, EvaluateAtIndex, FieldAverage, FieldMinAndMax, EvaluateOnDomain, FieldVariance, and Compare are now Generic[_T]. Their _Inputs/_Outputs inner classes carry the type parameter so e.g. .point.vector(...) returns FieldAverage[VectorSocket] and .o.mean resolves to VectorSocket.

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 _DomainFactory inner classes (including CaptureAttribute) so the type checker can resolve their return types.
  • SocketAccessor identifier lookup fix — Added a normalised-identifier pass (normalize_name(id)) so attribute access like .i.value_001 correctly resolves Blender identifiers such as Value_001 that cannot be round-tripped through denormalize_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) and len(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 component

In 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: GeometrySocket

NodeGroupBuilder — 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) >> out

g.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.py was split into a builder/ package for maintainability; VectorSocketLinker was renamed to VectorSocket. (#35)
  • Internal type aliases cleaned up — InputFloat replaces TYPE_INPUT_VALUE etc. (#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 node

Bug 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 MenuSwitch node 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.

Literal type hints for menu sockets (#17)

Enum choices on menu sockets are exposed as Literal type hints, giving IDE auto-complete on string arguments like data_type="FLOAT".