NodeTreeService

February 7, 2026 · View on GitHub

Overview

NodeTreeService provides utility functions for working with Blender 4+ style shader node tree interface sockets. It offers a clean API for querying, checking, and creating input and output sockets on node trees (node groups).

In Blender 4.0, the node tree interface API changed significantly. Node group inputs and outputs are now managed through node_tree.interface.items_tree rather than the older node_tree.inputs and node_tree.outputs collections. NodeTreeService abstracts these changes, providing a consistent interface for socket operations.

The service is focused specifically on node tree interface sockets—the external sockets visible when a node group is used as a node in another tree. For internal node socket operations (the actual connections between nodes within a tree), see NodeService.

NodeTreeService is a dependency of NodeService and is used whenever node groups need their interfaces configured or queried.

All methods are static; the class should never be instantiated.

Source

src/mpfb/services/nodetreeservice.py

Dependencies

DependencyUsage
LogServiceLogging via LogService.get_logger("services.nodetreeservice")

Socket Types

Common socket type identifiers for use with create_socket():

TypeDescription
NodeSocketFloatScalar float value
NodeSocketFloatFactorFloat clamped to 0-1 range
NodeSocketIntInteger value
NodeSocketBoolBoolean value
NodeSocketVector3D vector
NodeSocketColorRGBA color
NodeSocketShaderShader output
NodeSocketStringText string
NodeSocketImageImage reference
NodeSocketGeometryGeometry data
NodeSocketCollectionCollection reference
NodeSocketObjectObject reference

Public API

Socket Queries

get_socket(node_tree, socket_name, in_out="INPUT")

Get an interface socket by name and direction.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket
in_outstr"INPUT"Socket direction: "INPUT" or "OUTPUT"

Returns: bpy.types.NodeTreeInterfaceSocket or None

Searches through node_tree.interface.items_tree for a socket matching both the name and direction.


get_input_socket(node_tree, socket_name)

Get an input socket by name.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket

Returns: bpy.types.NodeTreeInterfaceSocket or None

Convenience wrapper for get_socket() with in_out="INPUT".


get_output_socket(node_tree, socket_name)

Get an output socket by name.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket

Returns: bpy.types.NodeTreeInterfaceSocket or None

Convenience wrapper for get_socket() with in_out="OUTPUT".


Socket Existence Checks

has_socket(node_tree, socket_name, in_out="INPUT")

Check if a socket exists.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket
in_outstr"INPUT"Socket direction: "INPUT" or "OUTPUT"

Returns: boolTrue if the socket exists.


has_input_socket(node_tree, socket_name)

Check if an input socket exists.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket

Returns: boolTrue if the input socket exists.


has_output_socket(node_tree, socket_name)

Check if an output socket exists.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree to search
socket_namestrName of the socket

Returns: boolTrue if the output socket exists.


Socket Creation

create_socket(node_tree, socket_name, socket_type, in_out="INPUT")

Create a new interface socket.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree
socket_namestrName for the new socket
socket_typestrSocket type (e.g., "NodeSocketFloat")
in_outstr"INPUT"Socket direction: "INPUT" or "OUTPUT"

Returns: bpy.types.NodeTreeInterfaceSocket — The newly created socket.

Uses node_tree.interface.new_socket() to create the socket with the specified properties.


create_input_socket(node_tree, socket_name, socket_type)

Create a new input socket.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree
socket_namestrName for the new socket
socket_typestrSocket type

Returns: bpy.types.NodeTreeInterfaceSocket — The newly created input socket.


create_output_socket(node_tree, socket_name, socket_type)

Create a new output socket.

ArgumentTypeDefaultDescription
node_treebpy.types.ShaderNodeTreeThe node tree
socket_namestrName for the new socket
socket_typestrSocket type

Returns: bpy.types.NodeTreeInterfaceSocket — The newly created output socket.


Examples

Basic Socket Operations

from mpfb.services.nodetreeservice import NodeTreeService
import bpy

# Get or create a node group
node_tree = bpy.data.node_groups.get("MyNodeGroup")
if not node_tree:
    node_tree = bpy.data.node_groups.new("MyNodeGroup", "ShaderNodeTree")

# Check if a socket exists before creating
if not NodeTreeService.has_input_socket(node_tree, "Factor"):
    socket = NodeTreeService.create_input_socket(
        node_tree, "Factor", "NodeSocketFloat"
    )
    socket.default_value = 0.5
    socket.min_value = 0.0
    socket.max_value = 1.0

Creating a Complete Node Group Interface

from mpfb.services.nodetreeservice import NodeTreeService
import bpy

def setup_skin_shader_interface(node_tree):
    """Set up the interface sockets for a skin shader group."""

    # Input sockets
    inputs = [
        ("Base Color", "NodeSocketColor"),
        ("Roughness", "NodeSocketFloat"),
        ("Subsurface", "NodeSocketFloat"),
        ("Normal", "NodeSocketVector"),
    ]

    for name, socket_type in inputs:
        if not NodeTreeService.has_input_socket(node_tree, name):
            socket = NodeTreeService.create_input_socket(node_tree, name, socket_type)
            # Set defaults for float sockets
            if socket_type == "NodeSocketFloat":
                if name == "Roughness":
                    socket.default_value = 0.5
                elif name == "Subsurface":
                    socket.default_value = 0.1

    # Output sockets
    if not NodeTreeService.has_output_socket(node_tree, "Shader"):
        NodeTreeService.create_output_socket(node_tree, "Shader", "NodeSocketShader")

# Usage
skin_tree = bpy.data.node_groups.new("MPFB_Skin", "ShaderNodeTree")
setup_skin_shader_interface(skin_tree)

Querying Socket Properties

from mpfb.services.nodetreeservice import NodeTreeService

node_tree = bpy.data.node_groups["MyNodeGroup"]

# Get a socket and read its properties
factor_socket = NodeTreeService.get_input_socket(node_tree, "Factor")
if factor_socket:
    print(f"Name: {factor_socket.name}")
    print(f"Type: {factor_socket.socket_type}")
    print(f"Default: {factor_socket.default_value}")
    if hasattr(factor_socket, 'min_value'):
        print(f"Range: {factor_socket.min_value} - {factor_socket.max_value}")

Conditional Socket Creation

from mpfb.services.nodetreeservice import NodeTreeService

def ensure_node_group_has_sockets(node_tree, required_inputs, required_outputs):
    """Ensure a node group has all required sockets."""

    missing_inputs = []
    missing_outputs = []

    for name, socket_type in required_inputs:
        if not NodeTreeService.has_input_socket(node_tree, name):
            NodeTreeService.create_input_socket(node_tree, name, socket_type)
            missing_inputs.append(name)

    for name, socket_type in required_outputs:
        if not NodeTreeService.has_output_socket(node_tree, name):
            NodeTreeService.create_output_socket(node_tree, name, socket_type)
            missing_outputs.append(name)

    if missing_inputs:
        print(f"Created missing inputs: {missing_inputs}")
    if missing_outputs:
        print(f"Created missing outputs: {missing_outputs}")

# Usage
ensure_node_group_has_sockets(
    node_tree,
    required_inputs=[
        ("Color", "NodeSocketColor"),
        ("Strength", "NodeSocketFloat"),
    ],
    required_outputs=[
        ("Result", "NodeSocketColor"),
    ]
)

Iterating Over All Sockets

from mpfb.services.nodetreeservice import NodeTreeService
import bpy

def list_all_sockets(node_tree):
    """List all interface sockets on a node tree."""
    print(f"Sockets for '{node_tree.name}':")

    for item in node_tree.interface.items_tree:
        if isinstance(item, bpy.types.NodeTreeInterfaceSocket):
            direction = "IN" if item.in_out == "INPUT" else "OUT"
            print(f"  [{direction}] {item.name}: {item.socket_type}")

# Usage
for ng in bpy.data.node_groups:
    if ng.name.startswith("MPFB_"):
        list_all_sockets(ng)

Integration with NodeService

from mpfb.services.nodeservice import NodeService
from mpfb.services.nodetreeservice import NodeTreeService
import bpy

# Create a node group with interface
node_tree = bpy.data.node_groups.new("MixGroup", "ShaderNodeTree")

# Set up interface
NodeTreeService.create_input_socket(node_tree, "Color1", "NodeSocketColor")
NodeTreeService.create_input_socket(node_tree, "Color2", "NodeSocketColor")
NodeTreeService.create_input_socket(node_tree, "Factor", "NodeSocketFloat")
NodeTreeService.create_output_socket(node_tree, "Color", "NodeSocketColor")

# Create internal nodes (using NodeService)
input_node = node_tree.nodes.new("NodeGroupInput")
output_node = node_tree.nodes.new("NodeGroupOutput")
mix_node = NodeService.create_node(node_tree, "ShaderNodeMix", xpos=200)
mix_node.data_type = 'RGBA'

# Link internal nodes
node_tree.links.new(input_node.outputs["Color1"], mix_node.inputs["A"])
node_tree.links.new(input_node.outputs["Color2"], mix_node.inputs["B"])
node_tree.links.new(input_node.outputs["Factor"], mix_node.inputs["Factor"])
node_tree.links.new(mix_node.outputs["Result"], output_node.inputs["Color"])