Call operator from different script (multi-file addon)

Hello!

I am quite new to coding in blender, and I ran into an issue i couldn’t solve for while now.

I have a script where i have a Panel class, which draws my UI.
Now, for organization reasons, I want to write the Operator which will actually do something once the Button is pressed, in a separate class in an other script.
How do I do that? I tried to create an init.py for building a multi-file addon, but even after it registeres the modules from the scripts, i continue getting an error that the called module does not have the attribute “bl_idname” which it does!
I suspect there might be a problem with the module - class difference, but I just dont understand how I am supposed to do that correctly.
Can anyone help? I would really like to know what the correct approach to a multi-file addon is.

Thank you!

Greetings,
Aschenstern

you’ll need to post your code, we can’t help if we don’t have any context.

Hi!

Sure, here it is:

  1. StaticAnimation.py
import bpy
from bpy.props import* 
import mathutils 
from math import pi 
import sys

    
StaticAniTypeModule = sys.modules['StaticAnimationType']
print(StaticAniTypeModule)

class StaticAniPanel(bpy.types.Panel):

    bl_label = "Static Animation"
    bl_idname = "BIOBOX_PT_StaticAnimation"
    bl_space_type  = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Biobox"
    
    def draw(self, context):
        wm = context.window_manager
        layout = self.layout
        #layout.operator(Camera.bl_idname) 
        scene = context.scene
        
        layout.label(text="Set Timepoint of Interest (Imported Data):")
        tpinput = layout.row()
        tpinput.prop(wm,"tpImport")
        
        layout.label(text = "Choose Animation Type:")
        anirow = layout.row()
        
        anirow.operator(StaticAniTypeModule.bl_idname)
        anirow.operator("render.render")
                

def register():
    bpy.utils.register_class(SetAnimation)
    bpy.utils.register_class(StaticAniPanel)
    
def unregister():
    bpy.utils.unregister_class(SetAnimation)
    bpy.utils.unregister_class(StaticAniPanel) 
     

if __name__ == "__main__":
    register()
    
  1. StaticAnimationType.py
import bpy
from bpy.props import*

  
class StaticAnimationType(bpy.types.Operator):
    
    bl_idname =  "object.staticanitype" 
    bl_label = "Static Image"        
    
              
    def execute(self, context):
        print("Static Type")
 
        return {'FINISHED'}  
 
def register():
    bpy.utils.register_class(StaticAnimationType)
    
    
def unregister():
    bpy.utils.unregister_class(StaticAnimationType)
   
     

if __name__ == "__main__":
    register()

3.init.py

bl_info = {
    'name': 'Biobox',
    'category': 'All',
    'version': (0, 0, 1),
    'blender': (2, 8, 0)
}

    
import sys
import importlib
import os

cwd =os.getcwd()
modulesNames = ['StaticAnimation', 'StaticAnimationType','Biobox','Transparency']
imported_modules = {}

for currentModuleName in modulesNames:
        #modulesFullNames.update({__name__: currentModuleName})
        if currentModuleName in locals():
            print(f"Reloading"+ {currentModuleName})
            importlib.reload(locals()[currentModuleName])
            imported_modules[currentModuleName] = locals()[currentModuleName]
            print(imported_modules)
        else:
            exec("import {}".format(currentModuleName))
            imported_modules[currentModuleName] = locals()[currentModuleName]
            print(imported_modules)
        
classes = []
for module in imported_modules.values():
    for one in module.__dict__.values():
        if hasattr(one, "TAG_REGISTER"):
            classes.append(one)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    for cls in classes:
        bpy.utils.unregister_class(cls)

if __name__ == "__main__":
    register()        
        

I have some other scripts as well which I want to link the same way, but i didn’t start with that yet.
Thanks!

FWIW i think your code suffers from trying a bit too hard to be clever. Digging through sys.modules to find information is very powerful, but also problematic if you’re not experienced with how Python stores module data.

The reason you’re getting an error is because your panel is trying to create an operator button, but StaticAniTypeModule has no attribute called “bl_idname”, which is true. Remember that a module is the entire file, not the class, so you’re telling it to find a variable called “bl_idname” in the module’s global scope (which does not exist). Your code should technically work if you change it to StaticAniTypeModule.StaticAnimationType.bl_idname, but honestly I would just recommend using the actual string name of the operator. You don’t plan on changing it frequently do you? If not, you’re just obfuscating your code for no reason, and it will make maintaining your addon more difficult in the future.

Oh my god thank you so much!
That was my issue, I didn’t really understand the difference between module and class.

I would love to do it the simple way - but what do you mean by “using the actual string name of the operator”? The thing is, I don’t understand how to create a multi file add on ( which should be installable by other users in the future from a zip ) without the init.py. Is there a simple way to connect scripts, and install them accordingly? Maybe you know a good tutorial or can tell me a short version.
Thank you!

The layout.operator() panel function takes a string input. Currently you’re doing a bit of extra work to look it up, bit really it just wants a string. If you put the bl_idname in there directly it will also work: ‘object.staticanitype’

Alright, thank you very much!