Blender’s Python API lets you manipulate data like objects, materials, etc. in a blend file. But, have you ever wondered what would happen if you called the add operator + on two objects? Or when you multiply * an object by a number?

Dunders in Blender

Of course, Blender would throw an error saying unsupported operand types. 🙂

But, if you think about it, whenever you call an operator like the + operator, Python is calling the respective double underscore method.

Blender throws an error because the Object class, bpy.types.Object, doesn’t implement multiplication or addition. But, this doesn’t mean Blender forbids us from adding this functionality to objects.

Just think about for a second what multiplying an object with an integer would mean in Blender’s context? 🤔

Of course, the closest thing we can relate to is duplicating an object. Multiplying an object by an integer should duplicate it that many times.

Lets try it out!

Duplicate objects using multiply operator

Have a look at this code snippet.

def duplicate_ntimes(self, other):
    assert type(other) is int, "Expected an int"
    new_objs = []
    for i in range(other):
        new_obj = self.copy()
        new_objs.append(new_obj)
        context.collection.objects.link(new_obj)
    return new_objs

setattr(bpy.types.Object, '__mul__', duplicate_ntimes)

Here, the function duplicate_ntimes duplicates a given object, self, n times based on the second argument, other. But, if you notice the last line, you can see that we are assigning this function as the __mul__ method to bpy.types.Object.

Setting the __mul__ method using setattr adds it to the bpy.types.Object class so that next time when we multiply an object with an integer, this method will be invoked.

Now you can simply duplicate an object n times like this:

>>> obj * 3

By the way, don’t forget to add these imports. Otherwise this code snippet will raise an error.

import bpy
from bpy import context

Contextual behaviour using context managers

Okay, implementing multiplication was pretty easy. Now, lets park that for a moment and shift our focus to addition. Think about what addition could mean in Blender’s context.

The Join operation right? 😃

But, wait. What about booleans? Addition could also mean Boolean Union. Does that mean we have to choose one over the other? 🤔 Is there a way to have both?

There is a mechanism built into Python to handle issues like this. It’s called a context manager (different from Blender’s bpy.context) and you have definitely seen or used it before.

Anytime you read a file using the with statement, you are using a context manager. Context managers enable us to set something up, yield the execution to other statements, and eventually tear down the initial setup.

When reading a file using the with statement, the setup is opening the file. Then you do something else, and finally, the file is closed for you. This setup and teardown is handled using __enter__ and __exit__ methods respectively.

But, implementing something using these methods means writing a class and I am personally not fond of classes. 🙂

Python has yet another mechanism to solve this. It’s called contextlib – a standard library module to make the process of implementing context managers as simple as writing a function.

Simply import contextlib and try the following snippet:

@contextlib.contextmanager
def zbool(context, destructive=True):
    def __iadd__(self, other):
        bpy.context.view_layer.objects.active = self
        mod = self.modifiers.new('ZBoolean', 'BOOLEAN')
        mod.operation = 'UNION'
        mod.object = other
        
        if destructive:
            bpy.ops.object.modifier_apply(modifier=mod.name)
            bpy.data.objects.remove(other)
            return self
        else:
            other.display_type = 'BOUNDS'
            return self
    
    setattr(bpy.types.Object, '__iadd__', __iadd__)
    
    try:
        yield
    finally:
        del bpy.types.Object.__iadd__

In the above snippet, you can see we are defining a function called zbool which in turn defines the __iadd__ method and adds it to bpy.types.Object. The __iadd__ method is responsible for the += operator.

If you notice, after we use setattr, we are yielding control back to the user to do something. In this case, it is to use the += operator.

And once the user is done, we delete the __iadd__ attribute from the bpy.types.Object class. This way, the += operator performs a Boolean Union only inside this zbool context manager.

And we make this function as context manager using the decorator @contextlib.contextmanager from the contextlib.

Now, you perform Boolean Union operations simply like this:

with zbool(bpy.context, False) as z:
    obj = bpy.data.objects['Cube']
    obj += bpy.data.objects['Cube.001']
    obj += bpy.data.objects['Cube.002']

By the way, those cubes were produced by the previous multiply operation. 🙂

This way we can implement contextual behaviours for our operators.

And, that’s it for now.

Conclusion

A few things you can try:

  • Try implementing the join operation when += is called outside the context manager.
  • Think of what operators like ==, !=, !, /, etc. could mean in Blender.

Read more about Python’s double underscore methods in the Python documentation.

Learn more about Python’s advanced features from this talk by James Powell.