import bpy from bpy.types import NodeTree, Node, NodeSocket # 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" # Enum items list my_items = ( ('DOWN', "Down", "Where your feet are"), ('UP', "Up", "Where your head should be"), ('LEFT', "Left", "Not right"), ('RIGHT', "Right", "Not left"), ) my_enum_prop: bpy.props.EnumProperty( name="Direction", description="Just an example", items=my_items, default='UP', ) # 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, "my_enum_prop", text=text) # Socket color def draw_color(self, context, node): return (1.0, 0.4, 0.216, 0.5) # 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(Node, MyCustomTreeNode): # === 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 # http://wiki.blender.org/index.php/Doc:2.6/Manual/Extensions/Python/Properties 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', "!") self.outputs.new('NodeSocketColor', "How") self.outputs.new('NodeSocketColor', "are") self.outputs.new('NodeSocketFloat', "you") self.outputs.new('NodeSocketString', "My String Prop") def update(self): self.outputs['My String Prop'].default_value = self.my_string_prop # 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" # Derived from the Node base type. class MyCustomNodeSave(Node, MyCustomTreeNode): '''Custom save node''' bl_idname = 'CustomNodeSaveType' bl_label = 'Custom File Save' my_string_prop: bpy.props.StringProperty(name='Path') def init(self, context): self.inputs.new('NodeSocketString', "Data") self.outputs.new('NodeSocketString', "Data") def draw_buttons(self, context, layout): layout.label(text="Node Settings") layout.prop(self, "my_string_prop") def draw_buttons_ext(self, context, layout): layout.prop(self, "my_string_prop") def update(self): import time print('updated', str(time.time())) print('links') print(self.inputs['Data'].links) print('') if len(self.inputs['Data'].links) == 0: return link = self.inputs['Data'].links[0] print('receive from node: ', link.from_node.name, 'socket: ', link.from_socket.name) print('value: ', link.from_socket) # alternative way to get socket: link.from_node.outputs[link.from_socket.name] print(link.from_socket.default_value) file = open(self.my_string_prop, 'w') file.write(link.from_socket.default_value) file.close() ### Node Categories ### # Node categories are a python system for automatically # extending the Add menu, toolbar panels and search operator. # For more examples see release/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"), NodeItem("CustomNodeSaveType") ]), MyNodeCategory('OTHERNODES', "Other Nodes", items=[ # the node item can have additional settings, # which are applied to new nodes # NB: 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, MyCustomNode, MyCustomNodeSave ) 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__": try:unregister() except:pass register()