Batch registering multiple classes in blender 2.8

Actually Jacques having ‘auto_load.ignore’ - for ignoring some .py files, or folders would be cool. I will try to implement it.

Not sure if it’s an error in the auto_load script or in my addon:

All my custom nodes are inheriting only from my base node, not from bpy.types.Node:

class LuxCoreNodeMaterial(bpy.types.Node):
    ...

class LuxCoreNodeMatGlass(LuxCoreNodeMaterial):
    ...

Since the auto_load script only checks the direct base classes, it does not register these nodes.
Should auto_load recursively check all base classes of base classes, or should I change my addon like this:

class LuxCoreNodeMaterial:
    ...

class LuxCoreNodeMatGlass(LuxCoreNodeMaterial, bpy.types.Node):
    ...

I think I tried using the class LuxCoreNodeMatGlass(LuxCoreNodeMaterial): in the past and it did not work for me. So I also use the class LuxCoreNodeMatGlass(LuxCoreNodeMaterial, bpy.types.Node): approach in Animation Nodes.

However I just tested it again and found that it works now. So I’d say the bug is in auto_load.

Could you test if it works when you change line 98 from if any(base in base_types for base in cls.__bases__): to if any(issubclass(cls, base) for base in base_types):?

[EDIT:]
Ah, just remembered that then the problem is that it will try to register LuxCoreNodeMaterial as well, but it has no bl_idname, so it will fail… Maybe that was the reason I used the other approach…

This works.

No, this is fine, the old register_module did the same and it worked.
It’s a bit ugly to have base classes registered that are never actually instanced, but I can live with that.

I only noticed problems with the first approach once with node trees, so I wondered if it is discouraged in Blender?

What problems did you notice?

Just looked at Blenders code and seems like it uses the second approach, I have not found any example where it uses the first approach yet. (e.g. see here https://developer.blender.org/diffusion/B/browse/master/release/scripts/startup/bl_ui/properties_object.py)

Personally, I think the first approach should be discouraged. Being a bit more explicit about what is a node/operator/panel/… can’t hurt.

Also that probably makes the automatic class discovery faster. Not sure by how much though, maybe it is not noticeable.

Unfortunately I don’t remember the details, but I think it was about a BoolProperty in the base class. With the first approach the subclasses could not access the BoolProperty, or something like that.

Thanks, I will switch to the second approach. Thank you for investigating.

Again, not sure if I’m doing something wrong or if auto_load is incomplete:
I declare several classes in __init__.py files, and some of these are not registered by auto_load.
Should I move all class declarations out of __init__.py files, or should auto_load check these for classes?

To my knowledge, addon modules can’t read the init.py file.

If you want something registered in one, you do it manually.

Thanks, I’ll move them out into separate files.

To improve registering clases, that are not directly child of bpy.types.Operator, we can change line:
if any(base in base_types for base in cls.__bases__):

to:
if any(base in base_types for base in inspect.getmro(cls)):

This way if my CustomOperator class, is child of some ModdedOperator, which is child of bpy.types.Operator, above line will correctly register it.
@jacqueslucke, can add this to autoload.py ?
The answer found on: https://stackoverflow.com/questions/2611892/how-to-get-the-parents-of-a-python-class

Thanks for the suggestion. However, I’d rather not do this for now, because:

  • Operators should inherit from bpy.types.Operator directly. That seems to be a good practice.
  • The “ModdedOperator” would be recognized as normal operator as well. However, it should not be registered.

To make a “template” for operators just make a class that does not inherit bpy.types.Operator. The real operator then inherits your custom class as well as bpy.types.Operator.

@jacqueslucke
Hi!
Thanks for the script. I am working on the LuxCore Addon together with BYOB and there are some problems with subclasses in panels. These classes are supposed to be displayed inside the parent class, which is referenced with bl_parent_id in the child class. Sometimes the script tries to register a child class before the parent class is registered which results in an error at startup and the loading of addon fails.

class LUXCORE_RENDER_PT_sampling(RenderButtonsPanel, Panel):
    bl_label = "Sampling"

class LUXCORE_RENDER_PT_sampling_sub_samples(RenderButtonsPanel, Panel):
    bl_parent_id = "LUXCORE_RENDER_PT_sampling"
    bl_label = "Sub Samples"

The registration order seems not to be deterministic because sometimes the addon successfully loads and sometimes an error occurs. Also the registration order determines the display order inside the panel. Currently you cannot define the display order with the automatic registration which would be needed to display the UI elements in a logical order. Is there a way to detect child/parent dependencies in UI classes and to define the registration order, e.g. via the order inside the py file? Otherwise I will skip the UI files in the auto load script and register the UI classes manually.

2 Likes

Hey, haven’t tried it yet, but the script already has dependency sorting. You probably just have to add another kind of dependencies between classes.

@neo2068
I have solved it like this:

def iter_own_register_deps(cls, own_classes):
    yield from (dep for dep in iter_register_deps(cls) if dep in own_classes)

    if getattr(cls, "bl_parent_id", None):
        for other_cls in own_classes:
            if other_cls.__name__ == cls.bl_parent_id:
                yield other_cls

Commit
Not sure if it’s the best way to do it, but it works for now.

@BYOB @jacqueslucke
Thanks for looking into the problem. That solves the dependency problem. Do you have an idea for the general registration order of the classes? Each time I start Blender, the display order of the classes are different.

1 Like

Maybe we could add a custom variable to our panel classes that says below which other panel the panel should go, and then sort the classes accordingly in the auto_load script before registering.

Edit: Apparently bpy.types.Panel already has the bl_order property for this purpose: https://docs.blender.org/api/master/bpy.types.Panel.html#bpy.types.Panel.bl_order
However, it seems to work only on top-level panels, looks like subpanels don’t respect this.

Here is the code that respects the order:


And a few lines down, when adding the panel to the parent’s panel list, the order is not respected: https://github.com/sobotka/blender/blob/94af455e37ae6121f2af5987fb694eb7d44f6cde/source/blender/makesrna/intern/rna_ui.c#L378

@BYOB
Thanks for the hint. I will add the bl_order for parent panels. I had something similar in mind for the registration of the sub classes. I will give it a try and will post the results here.

1 Like

@BYOB @jacqueslucke

I solved the display/registration order of the sub classes with a custom variable, i.e. lux_predecessor. This variable defines the class which should be registered before the current class. This defines a unique registration/display order.

def iter_own_register_deps(cls, own_classes):
    yield from (dep for dep in iter_register_deps(cls) if dep in own_classes)

    if getattr(cls, "bl_parent_id", None):
        for other_cls in own_classes:
            if other_cls.__name__ == cls.bl_parent_id:
                yield other_cls

    if getattr(cls, "lux_predecessor", None):        
        for other_cls in own_classes:
            if other_cls.__name__ == cls.lux_predecessor:
                yield other_cls

Does this still work? I’m getting errors in master (2.81)

Please post the output from the terminal.