2
$\begingroup$

I am trying to generate dynamically operator classes.

The code below is based on the code at the end of this link

cls.CustomOp = []
for i in ['a', 'b']:
    idname = "object.operator_" + i

    def func(self, context):
        print("Hello World", self.bl_idname)
        return {'FINISHED'}

    opclass = type("DynOp" + i,
                   (bpy.types.Operator, ),
                   {"bl_idname": idname, "bl_label": "Test", "execute": func},
                   )
    bpy.utils.register_class(opclass)
    cls.CustomOp = cls.CustomOp + [opclass]

I have tried the code above in poll and draw contexts, but always the same :

"RuntimeError: register_class(...): can't run in readonly state 'DynOpa'".

What does readonly context mean? How to do it (and from where) in a non-readonly context?

$\endgroup$
  • $\begingroup$ The question for me is what is behind needing to dynamically generate operator classes? $\endgroup$ – Ray Mairlot Jun 27 '16 at 18:17
  • $\begingroup$ @RayMairlot at the base this is related to solve this blender.stackexchange.com/questions/56727/…. But that can be a more general question $\endgroup$ – lemon Jun 27 '16 at 18:25
  • 1
    $\begingroup$ You can not run any commands that change properties whithin a poll or a draw function. As far as i know the only context where you can change something is when an operator actually runs or within a callback function that is started "on property change" when you for example set an option in a panel. $\endgroup$ – Gaia Clary Jun 27 '16 at 19:52
  • $\begingroup$ ok @GaiaClary, so this kind of approach cannot work in the context of your question ? or is it possible to apply this in another context (before a poll or a draw) ? $\endgroup$ – lemon Jun 27 '16 at 19:56
2
$\begingroup$

As you found, you can't register operators during some actions such as drawing or polling. We can define custom operators within an addons register() or within an operators execute() method, you can also manually run your script from blender's text editor without any issue.

For brevity I'll replace your for loop with a function called create_custom_operator()

def create_custom_operator(i):
    idname = "object.operator_" + i
    .... # rest as above

You can then use it in an addons register() -

def register():
    for n in ['a','b','c']:
        create_custom_operator(n)

Or you could use it in an operator's execute(). Note that I use a file list to define the operators here, we can do this in an operator's execute but not while registering an addon as we can't access the filesystem during addon registration.

class MakeCustomOperators(bpy.types.Operator):
    """Create custom operators"""
    bl_idname = 'object.add_custom_ops'
    bl_label = 'Add operators'
    bl_description ='Dynamically create multiple operators'

    def execute(self, context):
        for f in os.listdir('/path/to/data'):
            create_custom_operator(f)
        return {'FINISHED'}

I have given a more complete example in answer to the question that inspired this question.

$\endgroup$
  • $\begingroup$ Thanks a lot. So for instance the list of custom operators could be defined either in the register part or in an execute function of a modal operator (in order to have this list dynamically updated), isn't it ? $\endgroup$ – lemon Jun 28 '16 at 14:14
  • $\begingroup$ Yes if you want to dynamically update the operators you will want to create an operator that will create them when run, it can read a file, list a directory or scan objects in the scene to define the list. This could be used for a refresh button in a panel or menu. If you keep a list of the operators you can remove the old ones before creating a new list so you don't get duplicates or stale operators. $\endgroup$ – sambler Jun 28 '16 at 14:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.