Inheritance and mixin classes vs. Blender operators

recently I was working on an addon that would offer functionality both in weight paint mode and in vertex paint mode. There were slight differences in functionality of course so I needed two operators but I wanted to keep most of the shared code in a single mixin class.

Sharing member functions was easy enough using a mixin class but for some reason for properties this didn't work. Any property I put in the mixin class was not accessible from the derived classes. What was happening here?


class mixin(bpy.types.Operator):
foo = FloatProperty()
def oink(self): pass

class operator1(mixin):
def execute(self, context):
self.oink() # ok
bar = self.foo # error :-(

However, if I didn't derive the mixin class from bpy.types.Operator all was well ...

class mixin:
foo = FloatProperty()
def oink(self): pass

class operator1(bpy.types.Operator, mixin):
def execute(self, context):
self.oink() # ok
bar = self.foo # ok too :-)

So what is happening here? Sure in the first scenario both the mixin and the derived class inherit from bpy.types.Operator but that shouldn't be a problem is it? after all, Python's inheritance model supports the diamond pattern.

The answer to this riddle is hidden in Blender's source code, quote:


/* the rules for using these base classes are not clear,
* 'object' is of course not worth looking into and
* existing subclasses of RNA would cause a lot more dictionary
* looping then is needed (SomeOperator would scan Operator.__dict__)
* which is harmless but not at all useful.
*
* So only scan base classes which are not subclasses if blender types.
* This best fits having 'mix-in' classes for operators and render engines.
*/
So upon creating an instance of a class that is derived from bpy.types.Operator only the class itself and any base classes that are not derived from Blender types are searched for properties. This will reduce needless traversal of base classes that do not define properties themselves and avoid duplicate searches in cases where there is a diamond pattern but it sure is confusing.

It does make sense though: instantiation of operators should be as cheap as possible because instantion happens each time a screen area with the operator controls is redrawn! And this can add up: merely moving your mouse can result in tens of instantiations.

So the moral of this story? Sure it's awkward, especially while it's not well documented, but it is an understandable design choice. There is one serious drawback though; if you have the mixin and your operators in the same file you cannot use register_module() because that function tries to register all classes even the mixin class and that will fail.

conclusion

When using inheritance with bpy.types.Operator derived classes and you want to define properties in a mixin class make sure that:

  • the mixin class does not derive from bpy.types.Operator,
  • the final classes do derive from bpy.types.Operator (and from the mixin of course),
  • you don't use register_module() but use register_class()
  • for each operator separately (but do not register the mixin)

No comments:

Post a Comment