Dev:2.5/Py/API/Dynamic Creation of Operators

提供: wiki
< Dev:2.5‎ | Py‎ | API
移動先: 案内検索

Introduction

In Blender 2.5 operators are automatically registered (using a metaclass), so Blender can find them. This is normally very convenient, but when you're creating operators dynamically it might cause problems.

Problems and solutions

Example of a normal operator declaration that is automatically registered correctly:

import bpy

class Foobar(bpy.types.Operator):
    ''''''
    bl_idname = "foo.bar"
    bl_label = "FooBar!"

    def execute(self, context):
        print("foobar")
        return {'FINISHED'}

When you're dynamically creating operators, you might do something like this (which doesn't work):

import bpy

my_names = ["Foo", "Bar"]

def invoke(self, context, event):
    print(self.bl_description)
    return{'FINISHED'}

for name in my_names:
    my_class = type(name, (bpy.types.Operator,), dict(bl_idname=name, bl_label=name+"!", bl_description=name))
    setattr(my_class, 'invoke', invoke)

When you try to run the operator you get the error: "invalid operator call". The reason is that the automatic registering (the metaclass) is done immediately upon creating the class. So right after the line "my_class = type(...)". Adding methods or attributes after that, for example using setattr(), is too late. So the main thing to remember is:
setattr() doesn't work with the automatic registration of operators.

Below are two ways of doing it correctly:

import bpy

my_names = ["Foo", "Bar"]

def my_invoke(self, context, event):
    print(self.bl_description)
    return{'FINISHED'}

for name in my_names:
    class my_class(bpy.types.Operator):
        bl_idname = name
        bl_label = name+"!"
        bl_description = name
        invoke = my_invoke
import bpy

my_names = ["Foo", "Bar"]

for name in my_names:
    class my_class(bpy.types.Operator):
        bl_idname = name
        bl_label = name+"!"
        bl_description = name
        
        def invoke(self, context, event):
            print(self.bl_description)
            return{'FINISHED'}

There is one final thing that might stop your script from working, and that's when the script is run as add-on. In your add-on you can't create operators from within the register() function.
Example of code that fails when run as add-on (but works correctly when run from the text-editor):

bl_addon_info = {
    "name": "FooBar",
    "category": "System"}

import bpy

def create_ops():
    my_names = ["Foo", "Bar"]
    for name in my_names:
        class my_class(bpy.types.Operator):
            bl_idname = name
            bl_label = name+"!"
            bl_description = name
            
            def invoke(self, context, event):
                print(self.bl_description)
                return{'FINISHED'}
    
def register():
    create_ops()

def unregister():
    pass

if __name__ == "__main__":
    register()

The solution is to simply create the operator classes in the module itself (this way they are defined when the module is imported), not in a function definition:

bl_addon_info = {
    "name": "FooBar",
    "category": "System"}

import bpy

my_names = ["Foo", "Bar"]
for name in my_names:
    class my_class(bpy.types.Operator):
        bl_idname = name
        bl_label = name+"!"
        bl_description = name
           
        def invoke(self, context, event):
            print(self.bl_description)
            return{'FINISHED'}
    
def register():
    pass

def unregister():
    pass



Summary

  • Don't use setattr() to set the poll/invoke/execute functions.
  • Don't create operators from within the register() function.



References

Discussion on the mailinglist
Correct link to script mentioned in above discussion