1
$\begingroup$

I would like to move my Import PBR menu function into a node category. I've setup a node category called Import, and listed the class of the Import Pbr function as a Node to be included in the category. However, the script is showing up in the Texture category. Instead, I would like it to show up along with my Custom Float node in the Import category. How do I move the script/function into this category?

Here is the working code and a reference image:

#NOTE: Run this code first then use SHIFT-A, below, to add Custom Float node type.

import bpy
from bpy.types import NodeTree, Node, NodeSocket
import bpy, blf, bgl
from bpy.types import Operator, Panel, Menu
from bpy.props import FloatProperty, EnumProperty, BoolProperty, IntProperty, StringProperty, FloatVectorProperty, CollectionProperty
from bpy_extras.io_utils import ImportHelper
from mathutils import Vector
from math import cos, sin, pi, hypot
from os import path
from glob import glob
from copy import copy


def node_mid_pt(node, axis):
    if axis == 'x':
        d = node.location.x + (node.dimensions.x / 2)
    elif axis == 'y':
        d = node.location.y - (node.dimensions.y / 2)
    else:
        d = 0
    return d


def get_nodes_links(context):
    space = context.space_data
    tree = space.node_tree
    nodes = tree.nodes
    links = tree.links
    active = nodes.active
    context_active = context.active_node
    # check if we are working on regular node tree or node group is currently edited.
    # if group is edited - active node of space_tree is the group
    # if context.active_node != space active node - it means that the group is being edited.
    # in such case we set "nodes" to be nodes of this group, "links" to be links of this group
    # if context.active_node == space.active_node it means that we are not currently editing group
    is_main_tree = True
    if active:
        is_main_tree = context_active == active
    if not is_main_tree:  # if group is currently edited
        tree = active.node_tree
        nodes = tree.nodes
        links = tree.links

    return nodes, links



# Implementation of custom nodes from Python
# Derived from the NodeTree base type, similar to Menu, Operator, Panel, etc.
class MyCustomTree(NodeTree):
    bl_idname = 'CustomTreeType'
    bl_label = 'Custom Node Tree'

# Defines a poll function to enable filtering for various node tree types.
class MyCustomTreeNode :
    @classmethod
    def poll(cls, ntree):
        b = False
        # Make your node appear in different node trees by adding their bl_idname type here.
        if ntree.bl_idname == 'ShaderNodeTree': b = True
        return b

# Derived from the Node base type.
class MyCustomNode(Node, MyCustomTreeNode):
    '''A custom node'''
    bl_idname = 'CustomNodeType'
    bl_label = 'Custom Float'
    bl_icon = 'INFO'

    def update_value(self, context):
        self.outputs["Float"].default_value = self.some_value
        self.update ()

    some_value = bpy.props.FloatProperty(default=0.0, update = update_value)

    def init(self, context):
        self.outputs.new('NodeSocketFloat', "Float")
        self.outputs["Float"].default_value = self.some_value

    def update(self):
        #Review linked outputs.
        try:
            out = self.outputs["Float"]
            can_continue = True
        except:
            can_continue = False
        if can_continue:
            if out.is_linked:
                # I am an ouput node that is linked, try to update my link.
                for o in out.links:
                    if o.is_valid:
                        o.to_socket.node.inputs[o.to_socket.name].default_value = self.outputs["Float"].default_value   #self.some_value

    # Additional buttons displayed on the node.
    def draw_buttons(self, context, layout):
        layout.prop(self, "some_value",text = '')

    # Optional: custom label
    # Explicit user label overrides this, but here we can define a label dynamically.
    def draw_label(self):
        return "Test Float"


class NWAddMultipleImages(Operator, ImportHelper):
    """Add multiple images at once"""
    bl_idname = 'node.nw_add_multiple_images'
    bl_label = 'Open Selected Images'
    bl_options = {'REGISTER', 'UNDO'}
    directory = StringProperty(subtype="DIR_PATH")
    files = CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})

    def execute(self, context):
        nodes, links = get_nodes_links(context)
        nodes_list = [node for node in nodes]
        if nodes_list:
            nodes_list.sort(key=lambda k: k.location.x)
            xloc = nodes_list[0].location.x - 220  # place new nodes at far left
            yloc = 0
            for node in nodes:
                node.select = False
                yloc += node_mid_pt(node, 'y')
            yloc = yloc/len(nodes)
        else:
            xloc = 0
            yloc = 0

        if context.space_data.node_tree.type == 'SHADER':
            node_type = "ShaderNodeTexImage"
        elif context.space_data.node_tree.type == 'COMPOSITING':
            node_type = "CompositorNodeImage"
        else:
            self.report({'ERROR'}, "Unsupported Node Tree type!")
            return {'CANCELLED'}

        new_nodes = []
        for f in self.files:
            fname = f.name

            node = nodes.new(node_type)
            new_nodes.append(node)
            node.label = fname
            node.hide = True
            node.width_hidden = 100
            node.location.x = xloc
            node.location.y = yloc
            yloc -= 40

            img = bpy.data.images.load(self.directory+fname)
            node.image = img

        # shift new nodes up to center of tree
        list_size = new_nodes[0].location.y - new_nodes[-1].location.y
        for node in new_nodes:
            node.select = True
            node.location.y += (list_size/2)
        return {'FINISHED'}

### Node Categories ###
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 target tree type
class MyNodeCategory(NodeCategory):
    @classmethod
    def poll(cls, context):
        b = False
        # Make your node appear in different node trees by adding their bl_idname type here.
        if context.space_data.tree_type == 'ShaderNodeTree': b = True
        return b

# all categories in a list
node_categories = [
    # identifier, label, items list
    MyNodeCategory("SOMENODES", "Import", items=[
        NodeItem("CustomNodeType"),("'node.nw_add_multiple_images'"),
        ]),
    ]


def multipleimages_menu_func(self, context):
    col = self.layout.column(align=True)
    col.operator(NWAddMultipleImages.bl_idname, text="Import PBR")
    col.separator()

def register():
    bpy.utils.register_class(MyCustomNode)
    nodeitems_utils.register_node_categories("CUSTOM_NODES", node_categories)

    bpy.types.NODE_MT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
    bpy.types.NODE_PT_category_SH_NEW_TEXTURE.prepend(multipleimages_menu_func)
    bpy.types.NODE_MT_category_CMP_INPUT.prepend(multipleimages_menu_func)
    bpy.types.NODE_PT_category_CMP_INPUT.prepend(multipleimages_menu_func)

    bpy.utils.register_module(__name__)

def unregister():
    nodeitems_utils.unregister_node_categories("CUSTOM_NODES")
    bpy.utils.unregister_class(MyCustomNode)

    bpy.types.NODE_MT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
    bpy.types.NODE_PT_category_SH_NEW_TEXTURE.remove(multipleimages_menu_func)
    bpy.types.NODE_MT_category_CMP_INPUT.remove(multipleimages_menu_func)
    bpy.types.NODE_PT_category_CMP_INPUT.remove(multipleimages_menu_func)

    bpy.utils.unregister_module(__name__)

def pre_frame_change(scene):
    if scene.render.engine == 'CYCLES':
        # Scan materials to see if I have a custom node within any of the trees.
        for m in bpy.data.materials:
            if m.node_tree != None:
                for n in m.node_tree.nodes:
                    if n.bl_idname == 'CustomNodeType':
                        print(n.bl_idname)
                        # One of our custom nodes, let's update it.
                        # When we set the value that will trigger an update inside the node.
                        # Even if we change it to the same value it was.
                        v = n.some_value
                        n.some_value = v

if __name__ == "__main__":
    register()
    bpy.app.handlers.frame_change_pre.append(pre_frame_change)

Tool Menu - Import PBR script, not showing in Import NodeCategory with Custom FLoat node

Import PBR Script showing in Textures instead of Import NodeCategory

$\endgroup$

1 Answer 1

2
$\begingroup$

I implemented a crude solution in LuxBlend: Link

class NodeItemMultiImageImport(NodeItemCustom):
    # NodeItemCustom is not documented anywhere so this code is a bit of guesswork
    def draw_operator(self, self2, layout, context):
        layout.operator("luxrender.import_multiple_imagenodes")

    def __init__(self, poll=None, draw=None):
        if draw is None:
            draw = self.draw_operator
        super().__init__(poll, draw)

It is embedded in a category like this:

luxrender_node_categories_material = [
    luxrender_node_category_material("LUX_TEXTURE_1", "Texture (1)", items=[
        # ...
        NodeItemMultiImageImport(),
        # ...
    ]),
]

As noted in the comment it is the result of trial and error since I could not find any documentation on NodeItemCustom. It seems like you can draw anything in the draw function (even icons and multiple lines work).

This is how it looks like (the "Import Multiple Images" entry is the operator):

enter image description here

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .