0
$\begingroup$

I'm getting the following error everytime I trigger a certain operator (open_node_Pie1):

Python: Traceback (most recent call last):

File "C:\Users\Kaspar\AppData\Roaming\Blender 
Foundation\Blender\3.2\scripts\addons\NodePiesEXPbackup.py", line 252, in execute
if context.preferences.addons['NodePies'].preferences.menu_amount == '1':
KeyError: 'bpy_prop_collection[key]: key "NodePies" not found'

'NodePies' refers to the addon name __name__ . I'm not sure what is causing this error, since the name is correct and also referenced correctly in the class defining preferences.menu_amount, node_Pie_Preferences (line 392). In the class causing the error I'm trying to call different pie menus depending on the value of menu_amount definded in the addon preferences.

Any help is appreciated, thanks in advance!

Code: (the "node type" classes can be ignored, since they aren't important for this problem, as far as I know)

bl_info = {
    "name": "NodePies",
    "author": "Olliboy",
    "version": (0, 3),
    "blender": (2, 80, 0),
    "location": "Shader Editor > W",
    "description": "Adds pie menus for adding nodes more quickly.",
    "warning": "",
    "doc_url": "",
    "category": "Node",
}

import bpy
from bpy.types import Menu, Operator

# node types --------------------------------------------------------------------

class addAO(bpy.types.Operator):
    """Add An Ambient Occlusion Node"""
    bl_idname = "ext.add_ao"
    bl_label = "Ambient Occlusion"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeAmbientOcclusion")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addMath(bpy.types.Operator):
    """Add A Math Node"""
    bl_idname = "ext.add_math"
    bl_label = "Math"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeMath")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addVMath(bpy.types.Operator):
    """Add A Vector Math Node"""
    bl_idname = "ext.add_v_math"
    bl_label = "Vector Math"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeVectorMath")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')

class addColorRamp(bpy.types.Operator):
    """Add A ColorRamp Node"""
    bl_idname = "ext.add_cr"
    bl_label = "ColorRamp"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeValToRGB")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')

class addGradient(bpy.types.Operator):
    """Add A Gradient Texture Node"""
    bl_idname = "ext.add_gradient"
    bl_label = "Gradient Texture"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeTexGradient")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addNoiseTex(bpy.types.Operator):
    """Add A Noise Texture Node"""
    bl_idname = "ext.add_noise_tex"
    bl_label = "Noise Texture"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeTexNoise")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addImgTex(bpy.types.Operator):
    """Add An Image Texture Node"""
    bl_idname = "ext.add_img_tex"
    bl_label = "Image Texture"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeTexImage")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addPrinBSDF(bpy.types.Operator):
    """Add A Principled BSDF Node"""
    bl_idname = "ext.add_prin_bsdf"
    bl_label = "Principled BSDF"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeBsdfPrincipled")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addSepXYZ(bpy.types.Operator):
    """Add A Separate XYZ Node"""
    bl_idname = "ext.add_sep_xyz"
    bl_label = "Separate XYZ"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeSeparateXYZ")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addSepRGB(bpy.types.Operator):
    """Add A Separate RGB Node"""
    bl_idname = "ext.add_sep_rgb"
    bl_label = "Separate RGB"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeSeparateRGB")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addObjInfo(bpy.types.Operator):
    """Add An Object Info Node"""
    bl_idname = "ext.add_obj_info"
    bl_label = "Object Info"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeObjectInfo")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')  
    
class addHueSat(bpy.types.Operator):
    """Add A Hue/Saturation Node"""
    bl_idname = "ext.add_hue_sat"
    bl_label = "Hue Saturation Value"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeHueSaturation")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')   

class addCombXYZ(bpy.types.Operator):
    """Add A Combine XYZ Node"""
    bl_idname = "ext.add_comb_xyz"
    bl_label = "Combine XYZ"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeCombineXYZ")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')   

class addBump(bpy.types.Operator):
    """Add A Bump Node"""
    bl_idname = "ext.add_bump"
    bl_label = "Bump"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeBump")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')  

class addNMap(bpy.types.Operator):
    """Add A Normal Map Node"""
    bl_idname = "ext.add_n_map"
    bl_label = "Normal Map"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeNormalMap")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addMapRange(bpy.types.Operator):
    """Add A Map Range Node"""
    bl_idname = "ext.add_map_range"
    bl_label = "Map Range"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeMapRange")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
class addDisplacement(bpy.types.Operator):
    """Add A Displacement Node"""
    bl_idname = "ext.add_dis"
    bl_label = "Displacement"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.node.add_node(type="ShaderNodeDisplacement")
        return bpy.ops.node.translate_attach_remove_on_cancel('INVOKE_DEFAULT')
    
#------------------------------------------------------------------------------
    
   
class open_node_Pie1(bpy.types.Operator):
    """Open The NodePie Menu"""
    bl_idname = "ext.open_pie1"
    bl_label = "Open Menu"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        if context.preferences.addons['NodePies'].preferences.menu_amount == '1':
            bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie")
            print("Pie1 Called")
         
        elif context.preferences.addons['NodePies'].preferences.menu_amount == '2':
            bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie2")
            print("Pie2 Called")
         
        elif context.preferences.addons['NodePies'].preferences.menu_amount == '3':
            bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie3")
            print("Pie3 Called")
        
        elif context.preferences.addons['NodePies'].preferences.menu_amount == '4':
            bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie4")
            print("Pie4 Called")
        
        
      
    
class open_node_Pie2(bpy.types.Operator):
    """Open The Secondary NodePie Menu"""
    bl_idname = "ext.open_pie2"
    bl_label = "Open Secondary Menu"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie2")
        print("Pie2 Called")
        return {'FINISHED'}
 
    
class open_node_Pie3(bpy.types.Operator):
    """Open The Tertiary NodePie Menu"""
    bl_idname = "ext.open_pie3"
    bl_label = "Open Tertiary Menu"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie3")
        print("Pie3 Called")
        return {'FINISHED'}   
       
    
class open_node_Pie4(bpy.types.Operator):
    """Open The Quaternary NodePie Menu"""
    bl_idname = "ext.open_pie4"
    bl_label = "Open Quaternary Menu"

    @classmethod
    def poll(cls, context):
        return context.active_object is not None

    def execute(self, context):
        bpy.ops.wm.call_menu_pie(name="OBJECT_MT_node_Pie4")
        print("Pie4 Called")
        return {'FINISHED'}


#----------------------------------------------------------------------------  


class node_Pie(Menu):
    bl_idname = "OBJECT_MT_node_Pie"
    bl_label = "Add Node"
    
    
    
    

    def draw(self, context):
        layout = self.layout
        pie = layout.menu_pie()
    
        pie.operator("ext.add_math")
        pie.operator("ext.add_v_math")
        #pie.operator("ext.open_pie2")
        pie.operator("ext.add_cr")
        pie.operator("ext.add_gradient")  
        pie.operator("ext.add_noise_tex")
        pie.operator("ext.add_sep_rgb")
        pie.operator("ext.add_sep_xyz")

        
            
        

class node_Pie2(Menu):
    bl_idname = "OBJECT_MT_node_Pie2"
    bl_label = "Add Node"

    def draw(self, context):
        layout = self.layout

        pie = layout.menu_pie()

        #pie.operator("ext.add_ao")
        pie.operator("ext.add_n_map")        
        pie.operator("ext.add_img_tex")            
        pie.operator("ext.add_obj_info")
        pie.operator("ext.add_hue_sat")
        pie.operator("ext.add_comb_xyz")
        pie.operator("ext.add_map_range")
        pie.operator("ext.add_dis")
        pie.operator("ext.add_bump")
        
        
class node_Pie3(Menu):
    bl_idname = "OBJECT_MT_node_Pie3"
    bl_label = "Add Node"

    def draw(self, context):
        layout = self.layout

        pie = layout.menu_pie()

        
        
        
class node_Pie4(Menu):
    bl_idname = "OBJECT_MT_node_Pie4"
    bl_label = "Add Node"

    def draw(self, context):
        layout = self.layout

        pie = layout.menu_pie()

        
        
#----------------------------------------------------------------



     
class node_Pie_Preferences(bpy.types.AddonPreferences):
    bl_idname = __name__
    
    menu_amount: bpy.props.EnumProperty(
        items=[
            ('1', '1 Menu', '', '', 0),
            ('2', '2 Menus', '', '', 1),
            ('3', '3 Menus', '', '', 2),
            ('4', '4 Menus', '', '', 3)
        ],
        default='2'
    )

    #menu_amount: IntProperty(
    #    name="Amount of Pie Menus",
    #    default=2, min=1, max=4
    #)


    def draw(self, context):
        layout = self.layout
      
        layout.label(text=" Coming Soon!", icon="FUND")
        row = layout.row()
        layout.label(text='Choose the amount of NodePie menus:')
        row = layout.row()
        row.prop(self, 'menu_amount', expand=True)    
        
        
        
from bpy.utils import register_class, unregister_class

classes = [
node_Pie,
node_Pie2,
node_Pie3,
node_Pie4,
open_node_Pie1,
open_node_Pie2,
open_node_Pie3,
open_node_Pie4,
node_Pie_Preferences,



addAO,
addMath,
addVMath,
addColorRamp,
addGradient,
addNoiseTex,
addImgTex,
addPrinBSDF,
addSepXYZ,
addSepRGB,
addObjInfo,
addHueSat,
addCombXYZ,
addBump,
addNMap,
addMapRange,
addDisplacement
]

addon_keymaps = []

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
        
  
    
    # Add the hotkey
    wm = bpy.context.window_manager
    kc = wm.keyconfigs.addon
    if kc:
        km = wm.keyconfigs.addon.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
        kmi = km.keymap_items.new(open_node_Pie1.bl_idname, type='W', value='PRESS')
        addon_keymaps.append((km, kmi))




def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)
        
    for km, kmi in addon_keymaps:
        km.keymap_items.remove(kmi)
    addon_keymaps.clear()


if __name__ == "__main__":
    register()

    #bpy.ops.wm.call_menu_pie(name="node_Pie")


$\endgroup$
1
  • $\begingroup$ Hello ! Most likely because something didn't register properly so the addon as a whole didn't register properly, so it's not in the addons dictionary. side note your node operators would greatly benefit from dynamically creating and registering classes eg blender.stackexchange.com/q/56741/86891 $\endgroup$
    – Gorgious
    Sep 11, 2022 at 20:13

1 Answer 1

1
$\begingroup$

This will only work if you register this as an add-on otherwise the entry is not added to the context preferences lookup dictionary. I suggest you work directly from within the addons folder or create a symlink to your working directory that way you only have to go to Blender Icon > System > Reload Scripts to see your edits live.

enter image description here

You can find the addon installation folder by following this link

enter image description here

Make sure your file is named "NodePies.py" otherwise this won't work, the entry isn't based on the addon name but on the python file on disk, or the zip file name if you're distributing an addon with multiple files.

enter image description here

PS : If you manage to make it work, you'll get an error because your operator must return a set, usually {"FINISHED"} for regular operators.

$\endgroup$

You must log in to answer this question.

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