blender/scripts/templates_py/custom_nodes.py
Brecht Van Lommel 7a395e2e7f Revert changes from main commits that were merged into blender-v4.1-release
The last good commit was f57e4c5b98c075f3dfc61faebbcb43c99a778956.

After this one more fix was committed, this one is preserved as well:
67bd678887d7f8aec9f3b23bbf1aaf29f80d0da4.
2024-03-18 15:04:12 +01:00

201 lines
6.5 KiB
Python

import bpy
from bpy.types import NodeTree, Node, NodeSocket, NodeTreeInterfaceSocket
# Implementation of custom nodes from Python
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
# Description string
'''A custom node tree type that will show up in the editor type list'''
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'CustomTreeType'
# Label for nice name display
bl_label = "Custom Node Tree"
# Icon identifier
bl_icon = 'NODETREE'
# Custom socket type
class MyCustomSocket(NodeSocket):
# Description string
"""Custom node socket type"""
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'CustomSocketType'
# Label for nice name display
bl_label = "Custom Node Socket"
input_value: bpy.props.FloatProperty(
name="Value",
description="Value when the socket is not connected",
)
# Optional function for drawing the socket input value
def draw(self, context, layout, node, text):
if self.is_output or self.is_linked:
layout.label(text=text)
else:
layout.prop(self, "input_value", text=text)
# Socket color
@classmethod
def draw_color_simple(cls):
return (1.0, 0.4, 0.216, 0.5)
# Customizable interface properties to generate a socket from.
class MyCustomInterfaceSocket(NodeTreeInterfaceSocket):
# The type of socket that is generated.
bl_socket_idname = 'CustomSocketType'
default_value: bpy.props.FloatProperty(default=1.0, description="Default input value for new sockets",)
def draw(self, context, layout):
# Display properties of the interface.
layout.prop(self, "default_value")
# Set properties of newly created sockets
def init_socket(self, node, socket, data_path):
socket.input_value = self.default_value
# Use an existing socket to initialize the group interface
def from_socket(self, node, socket):
# Current value of the socket becomes the default
self.default_value = socket.input_value
# Mix-in class for all custom nodes in this tree type.
# Defines a poll function to enable instantiation.
class MyCustomTreeNode:
@classmethod
def poll(cls, ntree):
return ntree.bl_idname == 'CustomTreeType'
# Derived from the Node base type.
class MyCustomNode(MyCustomTreeNode, Node):
# === Basics ===
# Description string
'''A custom node'''
# Optional identifier string. If not explicitly defined, the python class name is used.
bl_idname = 'CustomNodeType'
# Label for nice name display
bl_label = "Custom Node"
# Icon identifier
bl_icon = 'SOUND'
# === Custom Properties ===
# These work just like custom properties in ID data blocks
# Extensive information can be found under
# https://docs.blender.org/api/current/bpy.props.html
my_string_prop: bpy.props.StringProperty()
my_float_prop: bpy.props.FloatProperty(default=3.1415926)
# === Optional Functions ===
# Initialization function, called when a new node is created.
# This is the most common place to create the sockets for a node, as shown below.
# NOTE: this is not the same as the standard __init__ function in Python, which is
# a purely internal Python method and unknown to the node system!
def init(self, context):
self.inputs.new('CustomSocketType', "Hello")
self.inputs.new('NodeSocketFloat', "World")
self.inputs.new('NodeSocketVector', "!", use_multi_input=True)
self.outputs.new('NodeSocketColor', "How")
self.outputs.new('NodeSocketColor', "are")
self.outputs.new('NodeSocketFloat', "you")
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
print("Copying from node ", node)
# Free function to clean up on removal.
def free(self):
print("Removing node ", self, ", Goodbye!")
# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):
layout.label(text="Node settings")
layout.prop(self, "my_float_prop")
# Detail buttons in the sidebar.
# If this function is not defined, the draw_buttons function is used instead
def draw_buttons_ext(self, context, layout):
layout.prop(self, "my_float_prop")
# my_string_prop button will only be visible in the sidebar
layout.prop(self, "my_string_prop")
# Optional: custom label
# Explicit user label overrides this, but here we can define a label dynamically
def draw_label(self):
return "I am a custom node"
### Node Categories ###
# Node categories are a python system for automatically
# extending the Add menu, toolbar panels and search operator.
# For more examples see scripts/startup/nodeitems_builtins.py
import nodeitems_utils
from nodeitems_utils import NodeCategory, NodeItem
# our own base class with an appropriate poll function,
# so the categories only show up in our own tree type
class MyNodeCategory(NodeCategory):
@classmethod
def poll(cls, context):
return context.space_data.tree_type == 'CustomTreeType'
# all categories in a list
node_categories = [
# identifier, label, items list
MyNodeCategory('SOMENODES', "Some Nodes", items=[
# our basic node
NodeItem("CustomNodeType"),
]),
MyNodeCategory('OTHERNODES', "Other Nodes", items=[
# the node item can have additional settings,
# which are applied to new nodes
# NOTE: settings values are stored as string expressions,
# for this reason they should be converted to strings using repr()
NodeItem("CustomNodeType", label="Node A", settings={
"my_string_prop": repr("Lorem ipsum dolor sit amet"),
"my_float_prop": repr(1.0),
}),
NodeItem("CustomNodeType", label="Node B", settings={
"my_string_prop": repr("consectetur adipisicing elit"),
"my_float_prop": repr(2.0),
}),
]),
]
classes = (
MyCustomTree,
MyCustomSocket,
MyCustomInterfaceSocket,
MyCustomNode,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
nodeitems_utils.register_node_categories('CUSTOM_NODES', node_categories)
def unregister():
nodeitems_utils.unregister_node_categories('CUSTOM_NODES')
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
if __name__ == "__main__":
register()