Use when developing a custom Sverchok node -- creating new node types, packaging as addons, or integrating with BMesh. Prevents the critical mistake of not calling self.outputs[i].sv_set() with correct nesting levels (data silently wrong). Covers full node lifecycle, socket creation, property management, BMesh integration, and registration patterns. Keywords: custom node, SverchCustomTreeNode, sv_get, sv_set, node registration, BMesh integration, node lifecycle, custom Sverchok node, addon, make my own node, create Sverchok plugin.
Use when developing a custom Sverchok node -- creating new node types, packaging as addons, or integrating with BMesh. Prevents the critical mistake of not calling self.outputs[i].sv_set() with correct nesting levels (data silently wrong). Covers full node lifecycle, socket creation, property management, BMesh integration, and registration patterns. Keywords: custom node, SverchCustomTreeNode, sv_get, sv_set, node registration, BMesh integration, node lifecycle, custom Sverchok node, addon, make my own node, create Sverchok plugin.
license
MIT
compatibility
Designed for Claude Code. Requires Blender 4.0+/5.x with Sverchok v1.4.0+.
metadata
{"author":"OpenAEC-Foundation","version":"1.0"}
sverchok-impl-custom-nodes
Quick Reference
Custom Node Inheritance
Every Sverchok custom node MUST inherit from both SverchCustomTreeNode and bpy.types.Node:
SverchCustomTreeNode provides the mixins UpdateNodes, NodeUtils, NodeDependencies, and NodeDocumentation.
Critical Warnings
NEVER call process() directly — ALWAYS let the update system invoke it via updateNode or tree.force_update().
NEVER use bpy.propsupdate= callbacks other than updateNode for triggering node re-evaluation — custom callbacks bypass the Sverchok update system.
NEVER mutate data returned by sv_get() unless you used deepcopy=True (the default) — mutating shared cache data corrupts upstream outputs.
NEVER create sockets outside of sv_init() or sv_update() — socket creation during process() causes infinite update loops.
ALWAYS use updateNode from sverchok.data_structure as the update= callback on ALL bpy.props properties that affect node output.
ALWAYS check self.outputs[name].is_linked before computing — skip processing when no downstream node consumes the output.
ALWAYS call bm.free() after using a BMesh — leaked BMesh objects cause memory leaks that persist until Blender restarts.
ALWAYS wrap socket data in the correct nesting level: [[data]] for single-object output, [[data1], [data2]] for multi-object.
Decision Tree
Writing a custom Sverchok node?
├── Simple data transformation → Minimal node template (Pattern 1)
├── Needs UI controls → Add sv_draw_buttons (Pattern 3)
├── Geometry processing → BMesh integration pattern (Pattern 5)
├── Performance-critical → NumPy vectorized pattern (Pattern 6)
└── External add-on → Registration pattern (Pattern 7)
Choosing socket creation method?
├── Fixed sockets → Direct creation in sv_init()
├── Socket with default property → sv_new_input() with prop_name
├── Mandatory input (error if empty) → sv_new_input() with is_mandatory=True
└── Dynamic socket count → multi_socket() in sv_update()
Node not updating?
├── Property changed but no effect → Check update=updateNode on bpy.props
├── Animation not triggering → Set is_animation_dependent = True
├── Scene changes ignored → Set is_scene_dependent = True
└── Missing library → Set sv_dependencies = {'library_name'}
Essential Patterns
Pattern 1: Minimal Custom Node Template
# Blender 4.0+/5.x with Sverchok v1.4.0+import bpy
from bpy.props import FloatProperty
from sverchok.node_tree import SverchCustomTreeNode
from sverchok.data_structure import updateNode, match_long_repeat
classSvScaleVerticesNode(SverchCustomTreeNode, bpy.types.Node):
"""
Triggers: scale transform multiply
Tooltip: Scales vertices by a factor
Multiplies all input vertices by a scale factor.
"""
bl_idname = 'SvScaleVerticesNode'
bl_label = 'Scale Vertices'
bl_icon = 'NONE'
scale_factor: FloatProperty(
name='Scale', default=1.0, update=updateNode
)
defsv_init(self, context):
self.inputs.new('SvVerticesSocket', 'Vertices')
self.inputs.new('SvStringsSocket', 'Scale').prop_name = 'scale_factor'self.outputs.new('SvVerticesSocket', 'Vertices')
defprocess(self):
ifnotself.outputs['Vertices'].is_linked:
return
verts = self.inputs['Vertices'].sv_get(default=[[]])
scale = self.inputs['Scale'].sv_get()
verts, scale = match_long_repeat([verts, scale])
result = []
for vert_list, scale_list inzip(verts, scale):
scaled = [(v[0]*s, v[1]*s, v[2]*s)
for v, s inzip(vert_list, scale_list)]
result.append(scaled)
self.outputs['Vertices'].sv_set(result)
defregister():
bpy.utils.register_class(SvScaleVerticesNode)
defunregister():
bpy.utils.unregister_class(SvScaleVerticesNode)
Pattern 2: Node Docstring Format
classSvMyNode(SverchCustomTreeNode, bpy.types.Node):
"""
Triggers: keyword1 keyword2 keyword3
Tooltip: Short description shown on hover
Longer description of the node functionality.
"""
Triggers: Space-separated keywords for the node search menu (Shift+S). ALWAYS include the most common terms users would search for.
Tooltip: One-line description shown on node hover. ALWAYS keep under 80 characters.
Pattern 3: Node Lifecycle Methods
# Blender 4.0+/5.x with Sverchok v1.4.0+classSvMyNode(SverchCustomTreeNode, bpy.types.Node):
bl_idname = 'SvMyNode'
bl_label = 'My Node'
my_prop: FloatProperty(name='Value', default=1.0, update=updateNode)
defsv_init(self, context):
"""Called ONCE when node is created. Create sockets here."""self.inputs.new('SvStringsSocket', 'Input')
self.outputs.new('SvStringsSocket', 'Output')
defprocess(self):
"""Called on each evaluation. Read inputs, compute, write outputs."""ifnotself.outputs['Output'].is_linked:
return
data = self.inputs['Input'].sv_get(default=[[0.0]])
self.outputs['Output'].sv_set(data)
defsv_update(self):
"""Called on tree topology changes (links added/removed).
Use for dynamic socket type changes. NEVER read/write socket data here."""passdefsv_copy(self, original):
"""Called when node is duplicated. Reset instance-specific state."""passdefsv_free(self):
"""Called when node is deleted. Release external resources."""passdefsv_draw_buttons(self, context, layout):
"""Draw UI elements in the node body."""
layout.prop(self, 'my_prop')
defsv_draw_buttons_ext(self, context, layout):
"""Draw extended UI in the sidebar properties panel (N-panel)."""self.sv_draw_buttons(context, layout)
defsv_init(self, context):
# Links socket to a bpy.props property for default displayself.sv_new_input('SvStringsSocket', 'Count',
prop_name='count_prop', hide_safe=True)
# Mandatory: raises SvNoDataError if no data availableself.sv_new_input('SvVerticesSocket', 'Vertices',
is_mandatory=True)
Available socket types
Socket Type
Data
Color
SvStringsSocket
Numbers, strings, generic lists
Green
SvVerticesSocket
Vertex coordinates (x,y,z)
Yellow
SvMatrixSocket
4x4 matrices
Lilac
SvColorSocket
RGBA colors
Dark yellow
SvQuaternionSocket
Quaternions
Purple
SvObjectSocket
Blender object references
Orange
SvSurfaceSocket
Surface objects
Blue
SvCurveSocket
Curve objects
Cyan
SvSolidSocket
Solid objects (FreeCAD)
Gray
SvFilePathSocket
File paths
Light gray
SvDictionarySocket
Python dicts
Dark green
Pattern 5: Standard Process Method
The standard process() method follows a 5-step pattern:
# Blender 4.0+/5.x with Sverchok v1.4.0+defprocess(self):
# 1. Output check — skip if nothing connectedifnotany(s.is_linked for s inself.outputs):
return# 2. Read inputs with safe defaults
verts = self.inputs['Vertices'].sv_get(default=[[]])
scale = self.inputs['Scale'].sv_get(default=[[1.0]])
# 3. Match input list lengths
verts, scale = match_long_repeat([verts, scale])
# 4. Process each object
result_verts = []
for vert_list, scale_list inzip(verts, scale):
vert_list, scale_list = match_long_repeat([vert_list, scale_list])
new_verts = [(v[0]*s, v[1]*s, v[2]*s)
for v, s inzip(vert_list, scale_list)]
result_verts.append(new_verts)
# 5. Set outputsself.outputs['Vertices'].sv_set(result_verts)
Pattern 6: BMesh Integration
# Blender 4.0+/5.x with Sverchok v1.4.0+from sverchok.utils.sv_bmesh_utils import bmesh_from_pydata, pydata_from_bmesh
import bmesh
defprocess(self):
ifnotself.outputs['Vertices'].is_linked:
return
verts = self.inputs['Vertices'].sv_get()
edges = self.inputs['Edges'].sv_get(default=[[]])
faces = self.inputs['Faces'].sv_get(default=[[]])
out_v, out_e, out_f = [], [], []
for v, e, f inzip(verts, edges, faces):
bm = bmesh_from_pydata(v, e, f, normal_update=True)
# Perform BMesh operations
bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=1)
new_v, new_e, new_f = pydata_from_bmesh(bm)
bm.free() # ALWAYS free BMesh after use
out_v.append(new_v)
out_e.append(new_e)
out_f.append(new_f)
self.outputs['Vertices'].sv_set(out_v)
self.outputs['Edges'].sv_set(out_e)
self.outputs['Faces'].sv_set(out_f)