Batch registering multiple classes in blender 2.8


#1

Based on this link: https://wiki.blender.org/wiki/Reference/Release_Notes/2.80/Python_API/Addons#Registration
We have to to create list of classes for registering. But with big addons this is lot of repetitive work - manually creating list of 20 - 40 it time consuming, and there is easy way to make mistake.
Is there better, automated way to create class list? Some script that goes through all .py files in current addon directory and creates this list automatically?


#2

have you investigated the link at the bottom?

If you have an addon with many classes, the list can be generated using this patch: https://developer.blender.org/P455


#3

It seems this patch is not included in blender 2.8.


#4

For Animation Nodes I’ll try to automate the registration process again. So far it seems to work well, but I did not test it much yet.

Also this is currently just a test implementation, maybe it helps anyway (scroll to the bottom): https://github.com/JacquesLucke/animation_nodes/blob/blender2.8/animation_nodes/init.py


#5

Thanks @jacqueslucke. It worked with some small tweak.
In iter_register_dependencies() - this method will add ‘Object’ cls type to class list dependencies, if I use
PointerProperty( name='ObjPtr', type=bpy.types.Object)
In my addon.
But we do not want do add ‘Object’ class in register() function, so I just made sure we skip PointerProperties if
type.__name__ == 'Object'.
With additional check :
if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty) and value[1]['type'].__name__ != 'Object': #skip pointer properties if type = bpy.types.Object
I guess this I should check for PointerProperty it type is in [‘Material’, ‘Object’] or other type of data.
Thanks for the help.


#6

Ah good point. I did not use these properties in AN yet for compability reasons but this should be fixed indeed, thank you :slight_smile:


#7

@Kia,

Does this mean it needs a custom build? Im not sure i understand the patch part


#8

Hey again,

yesterday I wrote a new, cleaner version of this auto-load functionality.
I plan to use it in a template in my VS Code extension, however I need to do some more testing first. Maybe you want to test it as well.

You can download the file here: https://gist.github.com/JacquesLucke/11fecc6ea86ef36ea72f76ca547e795b

Just put it into your main addon directory.
Then in the __init__.py file do this:

from . import auto_load

auto_load.init()

def register():
    auto_load.register()

def unregister():
    auto_load.unregister()

This should automatically

  1. import all modules in your addon
  2. discover all classes, that need to be registered
  3. sort the classes in case there are dependencies between them (e.g. when you use bpy.props.CollectionProperty
  4. register all classes
  5. call a register function in all modules that have one (except in the main __init__.py and in auto_load.py)

Limitations:

  • Can’t register keymaps, handlers, … You have to do that manually in a register function in any module. Maybe I’ll provide utilities to register these things later as well.
  • You can’t have classes you want to register in you main __init__.py file. However I think this is actually good.

#9

Works fine for me. a


#10

Could you make something like that with keymaps too?


#11

I don’t think that something like that works well for keymaps. Because they can’t really be searched for that easily.

To make this work, we need some kind of additional container class or function that can be discovered automatically.


#12

I mean, a code we could copy/paste like this one.


#13

Thanks for this script!
It seems to work fine on my addon, just one thing:

In one file, I am doing this:

from bpy.types import OperatorFileListElement
...
class LUXCORE_OT_import_multiple_images(bpy.types.Operator, ImportHelper):
    files: CollectionProperty(name="File Path", type=OperatorFileListElement)

And the auto_load script tries to register OperatorFileListElement, probably because it’s used in the CollectionProperty, but fails.
I added a try/except and this is the exception:

Could not register class: <class 'bpy.types.OperatorFileListElement'>
register_class(...): already registered as a subclass

I’ll try to look into this later, but currently I’m more concerned with getting the rest of my addon to run :slight_smile:


#14

Ah thank you very much, this is exactly the kind of mistakes I was looking for here. Will fix it and upload a new version of the script.


#15

I updated the script. Please check if it works now.


#16

Yes it works.
That was fast!

By the way, you should also add "RenderEngine" here: https://gist.github.com/JacquesLucke/11fecc6ea86ef36ea72f76ca547e795b#file-auto_load-py-L119


#17

Done! Let me know if your find more errors like that :slight_smile:


#18

Interesting! When I was making complex addons for pre-2.8 blender, I came up with a somewhat different solution. I made an AddonManager class (a instance of which was shared between addon’s modules) and used it to explicitly mark the classes I wanted to register/unregister. Something like this:

addon = AddonManager()

@addon.Panel
class OBJECT_PT_example_panel(bpy.types.Panel):

and so on. (I didn’t use topological sorting, just removed Pointer/Collection properties before registration of all classes and added them back afterwards.)

This approach doesn’t require registrable classes to be in the module globals (e.g. addon decorators work just as well for classes generated inside some factory function), and can allow some handy conveniences – for example, making an operator out of a function:

@addon.Operator(idname=“object.reset_vectors”, options={‘REGISTER’, ‘UNDO’}, description=“reset all axes”)
def Operator_Reset_Vectors(self, context, event, vectors=""):

For an example of use in actual addon code, you can look e.g. here.

What do you folks think about it? :slight_smile:


#19

Reminds me of the way it was done in the old LuxBlend addon.
There was an Addon class that kept track of classes that were marked with a decorator:


#20

An addon manager like this is also a good idea but I don’t want to use it my addons because:

  • Usually, when programming, I want to solve a problem and then not think about it anymore. With your approach it can still be quite easy to forget the decorator. I want that it just works.
  • It is true that my approach only finds classes that are in globals(). However I think that is not necessarily bad. I also use factory functions sometimes but usually I need even more flexibility in these cases. E.g. when I want to create new operators while the addon is registered already. In these cases I want to be explicit about when classes are (un)registered.
  • I want to provide this auto_load.py as a template for others and I don’t want that it is used in many different files. That could make updating the template much harder later on.
  • Developing developer tools is easier when no decorators are needed. Because otherwise an insert-operator-template function would have to know about how this class will be registered. So it would have to be smart, which is usually bad and should be avoided.

For these reasons I’d probably go with an opposite approach. Instead of providing an auto_load.Panel decorator, I create an auto_load.ignore decorate that can be used for classes that should not be registered automatically.

Having said that, such decorators are still very useful. In Animation Nodes I also have decorators to create operators from functions and to register draw handlers. I just want to make the common case as simple as possible. Maybe I’ll still add some decorators to auto_load. Could be a good way to handle registration of keymaps or so.


The idea of removing pointer and collection properties for registration is actually quite smart. I did not have this idea before. However I can also see some downsides (haven’t tested this approach yet, so maybe I’m wrong).

  • It might not work well when you have base classes that have properties shared by multiple Operators (maybe it works but the base classes will be messed up afterwards).
  • I don’t like the idea of removing stuff from the __annotations__ dict of classes. Not sure if that should be done by anyone.

So I’ll probably keep using the toposort to solve dependencies, that way I don’t have to mess around with classes other people write.