Skip to content

Instantly share code, notes, and snippets.

@AzureDVBB
Created July 17, 2018 21:22
Show Gist options
  • Save AzureDVBB/d3c2ad0b46ece40d180760f17ad94973 to your computer and use it in GitHub Desktop.
Save AzureDVBB/d3c2ad0b46ece40d180760f17ad94973 to your computer and use it in GitHub Desktop.

Revisions

  1. AzureDVBB created this gist Jul 17, 2018.
    184 changes: 184 additions & 0 deletions node_trees.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,184 @@
    # the little less documented way of adding a custom node tree
    # and populate it with nodes of varying types of I/O
    # sockets that work together, discombobulated

    # first we import the blender API
    import bpy


    # then we create the UI space, the node tree as it is called
    # but in actualy fact this is similar to a UI panel/menu
    # and mostly handled in the background in terms of creation
    # so let's define out custom node tree, inheriting from its base type
    class CustomNodeTree(bpy.types.NodeTree):
    # the docstring here is used to generate documentation but
    # also used to display a description to the user
    '''A custom node tree type'''
    # then we can give it a custom id to access it, if not given
    # it will use the classname by default
    bl_idname='CustomNodeTree'
    # the label is the name that will be displayed to the user
    bl_label='Custom Node Tree'
    # the icon that will be displayed in the UI
    # NOTE: check the blender dev plugins to see icons in text editor
    bl_icon='BLENDER'


    # that is all we needed to make a nodetree
    # first for convenience we make a class from which all our nodes
    # will inherit from, saving us some typing
    class CustomNode(bpy.types.Node):
    # this line makes the node visible only to the 'CustomNodeTree'
    # node tree, essentially checking context
    @classmethod
    def poll(cls, ntree):
    return ntree.bl_idname == 'CustomNodeTree'


    # now we start making a simple input node that uses builtin
    # blender properties, this is the simplest node
    # we define the node class that inherits from its mixin class
    class CustomSimpleInputNode(CustomNode):
    # we can add a docstring that will be interpreted as description
    '''A simple input node'''
    # optionally we define an id that we can reference the node by
    bl_idname = 'CustomSimpleInputNode'
    # we add a label that the node will show the user as its name
    bl_label = 'Simple Input Node'
    # we can also add an icon to it
    bl_icon = 'PLUS'

    # we can add properties here that the node uses locally
    # NOTE: does not get drawn automatically
    intProp = bpy.props.IntProperty()

    # init function that is automagickally is called when the
    # node is instantiated into the treem setup sockets here
    # for both inputs and outputs
    def init(self, context):
    # makes a new output socket of type 'NodeSocketInt' with the
    # label 'output' on it
    # NOTE: no elements will be drawn for output sockets
    self.outputs.new('NodeSocketInt', "output")

    # copy function is ran to initialize a copied node from
    # an existing one
    def copy(self, node):
    print("copied node", node)

    # free function is called when an existing node is deleted
    def free(self):
    print("Node removed", self)

    # draw method for drawing node UI just like any other
    # NOTE: input sockets are drawn by their respective methods
    # but output ones DO NOT for some reason, do it manually
    # and connect the drawn value to the output socket
    def draw_buttons(self, context, layout):
    # create a slider for int values
    layout.prop(self, 'intProp')

    # this method lets you design how the node properties
    # are drawn on the side panel (to the right)
    # if it is not defined, draw_buttons will be used instead
    #def draw_buttons_ext(self, context, layout):

    #OPTIONAL
    #we can use this function to dynamically define the label of
    # the node, however defining the bl_label explicitly overrides it
    #def draw_label(self):
    # return "this label is shown"


    # now to be able to see the nodes in the add node menu AND see it on the
    # tool shelf (left panel) we need to define node categories and then
    # register them, the rest is handled automagickally by blender
    # so first we import the utilities used for handling node categories
    import nodeitems_utils

    # now we can make our own category using the NodeCategory baseclass
    # as before we first make a mixin class to save space
    class CustomNodeCategory(nodeitems_utils.NodeCategory):
    # define the classmethod that tells blender which node tree
    # the categories made with this class belong to (is visible to)
    @classmethod
    def poll(cls, context):
    return context.space_data.tree_type == 'CustomNodeTree'


    # make a list of node categories for registration
    node_categories = [
    # NOTE: did not find documentation other then template script for it
    # esentially:
    # we instantiate a new 'nodeitems_utils.NodeCategory' class, that
    # has been extended with a poll method that makes sure that the
    # category and node only shows up in the desired node tree
    # The first argument is a string with its id we will use to access it by
    # the second argument is the name displayed to the user
    # the third argument is a list of (items) nodes that are under
    # that category, the list contains instances 'nodeitems_utils.NodeItem'
    CustomNodeCategory("CUSTOMINPUTNODES", "Custom Input Nodes", items=[
    # the nodes (items) in this category are instantiated in this list
    # with the 'nodeitems_utils.NodeItem' class, which can have
    # additional settings
    # the first argument is the node class idname we want to add
    # then there can be keyword arguments like label
    # another argument can be a 'settings' keyword argument
    # that takes a dictionary that can override default values of all
    # properties
    # NOTE: use 'repr()' to convert the value to string IMPORTANT
    nodeitems_utils.NodeItem("CustomSimpleInputNode",
    label="Simple Input", settings={"intProp":repr(1.0)}),
    # minimalistic node addition is like this
    nodeitems_utils.NodeItem("CustomSimpleInputNode"),
    ]),
    ]



    #finally we register our classes so we can install as plugin
    #to that end we create a list of classes to be loaded and unloaded
    classes=(
    CustomNodeTree,
    CustomSimpleInputNode,
    )

    # for loading we define the registering of all defined classes
    def register():
    # we register all our classes into blender
    for cls in classes:
    bpy.utils.register_class(cls)
    # we register the node categories with the node tree
    # the first argument is a string that is the idname for this collection
    # of categories
    # the second is the actual list of node categories to be registered under
    # this name
    nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)


    # for unloading we define the unregistering of all defined classes
    def unregister():
    # we unregister our node categories first
    nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
    # then we unregister all classes from the blender
    for cls in classes:
    bpy.utils.unregister_class(cls)


    # finally we make it runnable in the text editor for quick testing
    if __name__=='__main__':
    # during test running there is a bug where registering or adding
    # references to certain groups and categories cannot override
    # existing ones, here is a workaround
    # we enter a try..finally block
    try:
    # first try unregistering the existing category
    # WARNING: ALWAYS triple check that you unload the correct
    # things in this block, as it will not raise errors
    nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
    finally:
    # finally, wether it suceeded or not, we can now register it again
    # this essentially reloads existing/register changes made
    # and thanks to the nature of the finally block, is always run
    register()