Home Development Update Addons with both Blender 2.8 and 2.7 Support

Update Addons with both Blender 2.8 and 2.7 Support

by Patrick W. Crawford
update-blender-27-28-title

Comment below with your own tips for updates and support!

With the Blender 2.8 beta out, a lot of addons need an update. As a developer, perhaps you wishfully thought your scripts would work fine. You probably immediately found out this was not the case. Thus, you are facing the daunting task of updating your code for Blender 2.8.

  • How has the python api changed for Blender 2.8?
  • What changes do I need to make to just get this code working again in 2.8?
  • What are the best practices for dealing making these updates?
  • Can I maintain a single codebase that works for both Blender 2.7 and 2.8? (Yes, it’s possible and this post is centered around doing this)
  • Will things continue to break as Blender 2.8 evolves? (Yes, sorry about that)

If any of these questions hit home, then this guide is for you. Learn how to support Blender 2.8 addons and scripts – without ditching your 2.7 users.

Or check out one of these resources

Key Changes for Blender 2.8 Scripts & Addons

See this demo video using this resource guide and general best practices

Necessary updates will vary by script, but this likelihood-ordered list may cover most of what you need.

Minimum Blender version (2, 80, 0) strictly enforced

The easiest and first update you should make to support your addon in Blender 2.8: update the `bl_info: blender` field to say (2,80,0) or higher. Blender will show this warning if a lower version is displayed (such as this wrong notation of (2,8,0)):

update blender version label

Even if the rest of your code is working for 2.8, Blender still won’t let you enable or use the addon without this change.

This is a little bit of an unfortunate however. To use the same python/zip file for 2.8 and 2.7 installs, you force 2.7 users to see a “may not work” warning. My personal take is – yes, you should plan to distribute two versions of any addon; One with bl_info set for 2.8, and the other for 2.7. Beyond this, do your best to ensure this is the only difference between your 2.7 and 2.8 code! Keep reading on how to make this possible.

Breaking syntax: Python field annotations are here

Without going into too many of the details of this newer python 3.6 syntax notation, the short is that Blender is pushing developers from writing propname = bpy.props.BoolProperty() to using this syntax instead propname: bpy.props.BoolProperty() for class fields. This applies to properties parameters of operators or within property groups; this does not apply to properties directly registered e.g. to the scene or outside an operator.

Everything will actually still work fine without making this change, but you will find a plethora of console warnings. I was able to confirm that at least as of Jan 3rd 2019, the Blender Foundation has no timeline for converting these warnings into errors again(source) (earlier versions of blender 2.8 raised errors). That being said, it will likely return.

Annotation console warnings

The trouble is, Blender 2.7’s python (ie before python 3.6) does not recognize this new syntax. That means to make code work with both 2.7x and 2.8x, you might think you need to accept the warnings (and pray they turn into errors a long time from now). But, a workaround exists! Take a look at this function below, a sort of anti-pattern solution to this problem (credit to Darkblader24). You can continue to use the normal syntax with = instead of :, while preventing blender warnings.

def make_annotations(cls):
    """Converts class fields to annotations if running with Blender 2.8"""
    if bpy.app.version < (2, 80):
        return cls
    bl_props = {k: v for k, v in cls.__dict__.items() if isinstance(v, tuple)}
    if bl_props:
        if '__annotations__' not in cls.__dict__:
            setattr(cls, '__annotations__', {})
        annotations = cls.__dict__['__annotations__']
        for k, v in bl_props.items():
            annotations[k] = v
            delattr(cls, k)
    return cls

Use this on any class just before registering it via bpy.utils.register_class (check out the next section for full code sample). In the long term, it may be a good idea to just embrace the new way – but this is a viable workaround in the meantime.

No more blanket registration via bpy.utils.register_module

In the past, you could be somewhat lazy and put this line in your register function to auto register all classes in the entire addon, even across files bpy.utils.register_module(__name__).

Now, those days are gone. You have to register classes explicitly and one-by-one. This is definitely a move for the better – I have seen many addons that have had weird behaviors or even fail on enable due to issues of using this shortcut and classes registering in a random order.

See this recommended pattern, limiting code duplication:

# List of the name of the classes inherited from Blender types like AddonPreferences, Operator, Panel etc
# order matters, e.g. if those operators depend on MyPropertyGroup
classes = (
    MyAddonPreferences,
    MyPropertyGroup,
    ADDON_OT_some_operator,
    ADDON_OT_some_other_operator,
    ADDON_PT_some_panel
)

def register():
    for cls in classes:
        make_annotations(cls) # what is this? Read the section on annotations above!
        bpy.utils.register_class(cls)

def unregister():  # note how unregistering is done in reverse
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

Furthermore, if you are doing no other property registration or funny things like the make_annotations above, you can even use this builtin shortcut instead of manually defining register and unregister functions.

classes = (
    MyAddonPreferences,
    MyPropertyGroup,
    ADDON_OT_some_operator,
    ADDON_OT_some_other_operator,
    ADDON_PT_some_panel
)
register, unregister = bpy.utils.register_classes_factory(classes)

Explicit keyword arguments now required

This is part of a move to make developers more explicit and rigid in coding style. Explicit keyword arguments are now required for optional or positional function arguments. One example that very well may be the first error you see when enabling your 2.7x code is:

TypeError: UILayout.label(): required parameter "text" to be a keyword argument!

The change is as simple as going from layout.label("Hello world") to layout.label(text="Hello world"). The console is your friend, as it will tell you the specific line and updates needed. View the console window via Window > Toggle Console (if on Windows; on OSX or Linux, start blender from a command line terminal window).

This doesn’t just apply to UI code, so make the changes for operator calls and elsewhere – and get in a habit of always filling out these optional keyword arguments!.

Groups are now collections, no more layers

One of the major workflow shifts of Blender 2.8 is the movement from the previous visual layers and groups into the world of collections. At a first glance, most code can very simply be updated from bpy.data.groups to bpy.data.collections, but there are a few more nuances to understand:

  • All blender scenes by default will start with a collection bpy.data.collections['Collections']
  • Instead of the original 20 layers in 2.7, users will toggle visibility of collections or “View Layers”, which can be even nested sets of collections. Check out this page for more details, noting also the 2.8 concept of View Layers.
  • As you add new objects to a scene, you are simultaneously adding them to the scene’s default collection if it has one, or the scene’s master collection. Removing an object from all collections is the equivalent to removing it from the scene itself, much like in blender 2.7x we have context.scene.objects.unlink()
  • Thus, beware of code that makes assumptions of object visibility not being tied to group/collection membership!
  • Be also mindful of the concept of a scene’s “master collection” which is accessed via bpy.data.scene["Scene"].collection, which is not available through bpy.data.collections.

Blender layers 2.8

Getting a groups vs collections

Want to update your code to work with groups in 2.8? Try this snippet out, so it can keep working on blender 2.7 as well.

...
if hasattr(bpy.data, "collections"):
    mygroup = bpy.data.collections.get("MyGroup")
else:
    mygroup = bpy.data.groups.get("MyGroup")
...

It is a bit funny to be grabbing a collection and naming it as though it were a group, so as you make your updates consider also generalizing the terminology away from “groups” and “collections” where it makes sense.

Taking a look at layers

If you have 2.7 code dealing with changing viewport layers, you will have to make some adjustments to fit Blender 2.8. This page here has good 2.7 vs 2.8 example code, but how would you approach this in a cross-compatibility way? Here is one complete example that force uses collections in 2.8 to behave in a similar way as layers in 2.7:

def layers_get(object):
    """Gets membership of object in 0-19 numbered layers/collections

    returns: list of booleans with length of 20
    """
    if hasattr(object, "layers"):
        return object.layers
    else:
        obj_colls = object.users_collection # get all collections object is in
        collection_names = [coll.name for coll in obj_colls]
        return [str(i) in collection_names for i in range(20)] # ordered bool list


def layers_set(object, layers=[], context=None):
    """Assign layers or assign/crate collections with 2.7/2.8 support

    Arguments:
        object: valid object from bpy.data.objects
        layers: list of bools with a length of 20
    """
    if hasattr(object, "layers"):
        # Blender 2.7x branch
        object.layers = layers  # raises exception if not a bool list of len 20
        return
    
    # Blender 2.8x branch, apply layers checks to force consistency with 2.7
    if not context:
        context = bpy.context
    if len(layers) != 20:
        # artificially force consistency with 2.8 layer counts
        raise Exception("Length of layers should be 20")
    elif sum([not isinstance(n, bool) for n in layers])>0:
        # force that the list of layers is bools only
        raise Exception("All layers elements must be booleans")
    
    # Create collection and assign object
    for i, layer in enumerate(layers):
        if str(i) not in bpy.data.collections and layer is False:
            continue # collection doesn't exist, but not 'enabling layer' anyways
        elif str(i) not in bpy.data.collections and layer is True:
            collection = bpy.data.collections.new(name=str(i)) # create new one
            context.scene.collection.children.link(collection) # add to scene
        else:
            collection = bpy.data.collections[str(i)] # careful of linked libraries!
        
        # now assign "layer visibility" by adding or removing to named collection
        if layer is True:
            # add object to layer if not already present
            if object in collection.objects[:]:
                continue
            else:
                collection.objects.link(object)
        else:
            # remove object from layer if present
            if object not in collection.objects[:]:
                continue
            else:
                collection.objects.unlink(object)

This code block implements layers_get and layers_set methods. In 2.7, it simply toggles the visibility of each  viewport layer, which are unnamed. In Blender 2.8, these functions translate this list of booleans into dynamically generated collections with names like ‘0’, ‘1’, ‘2’ etc. It will then check inclusion in, add to, or remove objects from these collections. Below is an example using these two functions in both Blender 2.7 and 2.8:

object = bpy.data.objects['Cube']
layers = [True]*2 + [False]*18 # enables first two "layers"
layers_set(object, layers, bpy.context)
print(layers_get(object))

This approach is really force-feeding 2.7 style of layer management into 2.8, so I wouldn’t recommend using it directly as-is. Rather, aim to explicitly manage the collections/layers your addon cares about through functional collection names, and avoid generating unnecessary or unused collections.

Adding, instancing, and setting active objects

Adding objects to scene

In line with the changes to how collections function as a replacement for layers, so has adding objects and instances. Previously you link could existing objects to a scene (if not already there) by running

bpy.context.scene.objects.link(obj)  # (2.7) Links object to active scene

Now in blender 2.8, the equivalent is to add objects to either a collection used within a scene, or add them directly to the master collection attached to the scene itself

bpy.context.scene.collection.objects.link(obj)  # (2.8) Links object to scene master collection

Of course, you can wrap this with a if hasattr(context, "collections") to run the appropriate way to make this still work in blender 2.7 and 2.8.

Setting active objects

As for setting the active object, see below for the updates and multi-version compatible way to do this:

def set_active_object(context, obj):
    """Get the active object in a 2.7 and 2.8 compatible way"""
    if hasattr(context, "view_layer"):
        context.view_layer.objects.active = obj # the 2.8 way
    else:
        context.scene.objects.active = obj # the 2.7 way
    # note that `context.object` still works in 2.8 as a read-only way to get active objects

Instancing objects

There are also some notable changes in how you instance objects. There are very specific memory implications, so read up on this page for more details. 

Lamps renamed to Lights, and Hemis removed

Something to be aware of, instead of getting all light type objects via bpy.data.lights you now have you use bpy.data.lights. How about the cross compatible way of of, say, creating a new light source?

def new_light(name, lamp_type):
    """Create a lamp in a cross compatible way, and add to scene"""
    if hasattr(bpy.data, "lights"): # the 2.8 way
        if lamp_type not in ("POINT", "SUN", "AREA", "SPOT"):
            raise Exception("Invalid type of light for blender 2.8")
        return bpy.data.lights.new(name, lamp_type)
    else:
        if lamp_type not in ("POINT", "SUN", "AREA", "SPOT", "HEMI"):
            raise Exception("Invalid type of light for blender 2.7")
        return bpy.data.lamps.new(name, lamp_type)

Update checks for lamp types

Personally, I got tripped up in updating some code because I had type checks like if object.type == "LAMP":... because this wasn’t creating outright errors – but it of course wasn’t doing anything for my lights as their type name has changed. This snippet would be the best way to detect light/lamp-type objects in both 2.7 and 2.8:

if object.type in ("LIGHT", "LAMP"):
    ...

Additionally, as the section title indicates, there is no longer the type of light called “Hemi”. If you had a scene created in blender 2.7 with any Hemi lamps, they are converted to Sun lamps in blender 2.8. Take that into consideration if you have any code which deals with hemis, and certainly don’t go trying to create hemi-type lamps in blender 2.8!

Renamed context.user_preferences to context.preferences

Essentially overnight, this minor change broke half or more of addons already ported to 2.8. Earlier Blender 2.8 builds and 2.7 Blender accessed user preferences via bpy.context.user_preferences and then by selecting the addon package in question (often by using the [__package__]  variable). This has since been renamed to bpy.context.preferences.

Could I beg you to please not do a simple find:replace when making this update? The following code makes it silly easy to support all versions of 2.8 and 2.7 simultaneously!

def get_preferences(context=None):
    """Multi version compatibility for getting preferences"""
    if not context:
        context = bpy.context
    prefs = None
    if hasattr(context, "user_preferences"):
        prefs = context.user_preferences.addons.get(__package__, None)
    elif hasattr(context, "preferences"):
        prefs = context.preferences.addons.get(__package__, None)
    if prefs:
        return prefs.preferences
    else:
        raise Exception("Could not fetch user preferences")

Then use this function like my_addon_prefs = get_preferences(context)   Your addon has multiple files? Then stick this utility function into the file already shared with all the others, and then simply import the function into the other files as needed so you don’t need to duplicate the codeblock.

It is also worth noting that there has been more renaming in the area of user preferences. See this page for more of these changes, and remember you can follow a similar pattern as the above to make backwards compatible changes.

Some UI locations have changed

When I say UI location, what I really mean is for example the tabs of the 3D toolshelf you find in 2.79 is now on the right side of the 3D viewport. But not only that, sometimes the indicator names for these locations have also changed. For instance, to make a custom panel show up on the right-hand side toolshelf, you could write:

class ADDON_PT_my_toolshelf_panel(bpy.types.Panel):
    ...     
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS' if bpy.app.version < (2, 80) else 'UI'
    bl_category = "Tools"
    ...

Note how in blender 2.7x the space was called TOOLS but now it is called UI. The snippet above thus makes the code compatible for both blender 2.7x and 2.8x. I will say, try to contemplate that the actual UI workflows have changed too. Don’t just try to cram your UI into the closest place as before just for the sake of consistency.

Place your UI where it makes the most sense, and is consistent with new Blender 2.8 workflows.

Panel and menu name changes

In addition to region_type and space_type name changes, panels themselves have also changed in some cases. For instance, in 2.7x to append to the materials panel you would write bpy.types.Cycles_PT_context_material.append(my_ui_code) but now this has been renamed to bpy.types.CYCLES_PT_context_material.append(my_ui_code)  and bpy.types.EEVEE_MATERIAL_PT_context_material.append(my_ui_code) depending on your set render engine (note how case matters).

Remember, you can always go into the python console and use the autocomplete tool against bpy.types. Better yet, find an existing panel that is in the spot, and right click-edit script to get the UI code for that draw element. Looking higher up in this file will reveal the name of the panel you are looking for (shown below).

Want to make your panel-appending code work in both Blender 2.7 and 2.8? Then this is the codeblock for you:

if hasattr(bpy.types, "Cycles_PT_context_material"):
    bpy.types.Cycles_PT_context_material.append(my_ui_code)
elif hasattr(bpy.types, "CYCLES_PT_context_material"):
    bpy.types.CYCLES_PT_context_material.append(my_ui_code)
    bpy.types.EEVEE_MATERIAL_PT_context_material.append(my_ui_code)

Note again how I’m not doing blanket checks for bpy.app.version >= (2, 80), but directly checking whether the thing I want actually exists. See also this page for more details on best practices around making these UI change updates.

Handler changes, and introducing Timers

Changes to blender handlers

Handlers are a powerful way to control blender through more ways than just a user clicking a button. Several changes have been made in Blender 2.8, and it is good to understand what and why.

  • Scene update handlers have been removed, and most approximately replaced with `depsgraph_update_pre` and `depsgraph_update_post` (source). Note that some developers misused these scene handlers to run code normally not allowed during the restricted blender startup context. If this is you, take a look at timers below (or async threads).
  • Addition of redo_pre and redo_post handlers, which allow for better management fo redoing and undoing actions if needed. Examples of where this could be useful is in os operations. Another working example is here, used to address memory issues.
  • Removal of game_pre and game_post handlers, consistent with the removal of the game engine (to be born again another day).

Introducing: timers

Additionally, a new concept now exists called timers. You ca read more here, but these are great a way to do and manage asynchronous work in blender. Some example features:

  • Run a function in 5 seconds from now
  • Run a function every 2 seconds
  • Use timers to react to events in another thread.

One anecdote: asynchronous processes in blender 2.7x are possible, though operators are not always as stable or thread safe. This being said, you should plan to use standard python async libraries over these exclusive 2.8 timers to maintain 2.7 support. If you really want to use the new timers, gate them behind checks for availability via hasattr(bpy.app, “timers”).

Icons are mono-chromatic, and some removed, added, or redesigned

Pretty self explanatory. Not only are nearly all icons desaturated (or at least, reduced to a single color), several have been renamed, removed, or added. You can quickly this by enabling your code and navigating to your UI. Then, you can look into the console for errors about a given ICON not found.

The more thorough way to verify this would be to text search in your code for icon= and check each icon reference exists in the latest blender 2.8 builds. The Developer: Icon viewer addon is your friend (source of screenshots below)!

Icons for Blender 2.7 and 2.8

Changed icon designs and purpose

Even icons with unchanged names in 2.8 have an entirely new designs, so verify that it still makes sense to use it! Some specific examples I ran into:

  • Now missing: LOAD_FACTORY, closest but non-exact replacement for my state-saving use case was DISK_DRIVE.
  • Now missing: ZOOMIN, where ADD is the closest replacement
  • Icons like COLOR_RED and COLOR_GREEN still exist, but are now just the letters R and G instead of the actual colors. Where I was previously using the color to indicate “good” vs “issue”, the newer icons don’t really give that impression. Thus, for my 2.8 code I switched to use checkmark (FILE_TICK) and X instead. Your own use case will vary.

While I normally advocate for avoiding blanket version checks via bpy.app.version, in this case it is appropriate. In the example above where ZOOMIN icon exists in blender 2.7, and ADD in 2.8 I could write the code icon="ZOOMIN" if bpy.app.version < (2, 80) else "ADD".

One final piece to call out – blender has a much simpler, modern design now. Many places were icons existed before now only show text. Consider following suit and not shoving icons into your UI for the sake of having an icon!

Object selections and visibility have changed

Object selections

A small but very frequently used operation, getting and setting the selection state for an object has changed. Given an object like cube = bpy.data.objects["Cube"], you used to be able to get selection state via selected = cube.select and change it via cube.selection = True. Now, you have to use selected = cube.select_get() and cube.select_set(True) respectively.

To support 2.7x and 2.8, use this everywhere you read or assign object selections:

def select_get(object):
    """Multi version compatibility for getting object selection"""
    if hasattr(object, "select_get"):
    	return object.select_get()
    else:
        return object.select

def select_set(object, state):
    """Multi version compatibility for setting object selection"""
    if hasattr(object, "select_set"):
    	object.select_set(state)
    else:
        object.select = state

Note how I’m directly checking whether the attribute exists instead of checking against bpy.app.version!

Viewport visibility

In terms of object visibility, the change is more minor – instead of using object.hide for both setting and reading, you will need to use obj.hide_viewport. This is more parallel to to the render visibility counterpart obj.hide_render which is unchanged. You can use this simple function for both 2.7 and 2.8 compatibility:

def hide_viewport(object, state):
    """Multi version compatibility for setting the viewport hide state of an object"""
    if hasattr(object, "hide_viewport"):
        object.hide_viewport = state # where state is a boolean True or False
    else:
        object.hide = state

One performance consideration: worried the overhead of this extra hasattr conditional? I ran timing tests and found this custom select_set function adds ~20% processing time compared to the builtin method. I would argue this is small given the speed of selection setting, plus the cross compatibility benefit. Generally selection setting is not the “critical path” of code, and using this approach is still far quicker than using the bpy.ops.object.select_all operator (see this post for more details there).

Class & idname naming constraints

Allowed bl_idname for panels and menus

Update as of March 27, 2019: While class name registration is still not enforced, Blender has started to enforce the names of bl_idname’s for panels, menus, and UI Lists (and likely more).

  • See the error “…doesn’t contain ‘_MT_’ with prefix & suffix”? Rename your menu bl_idname to be e.g. AAA_MT_menu_name (menus are the row-based popups you find in the interface, such as the info > file dropdown)
  • See the error “…doesn’t contain ‘_PT_’ with prefix & suffix”? Rename your panel bl_idname to be e.g. AAA_PT_panel_name (panels are persistent areas within the interface, such as Transform section in the 3D view shelf)
  • See the error “…doesn’t contain ‘_UL_’ with prefix & suffix”? Rename your UI List bl_idname to be e.g. AAA_UL_panel_name (this is the list-like, scrollbar object you can place inside a panel, such as the list of materials on an object)
  • See the error “…doesn’t have upper case alpha-numeric prefix”? Rename your menu (or panel or UI list) bl_idname from aaa_MT_name to AAA_MT_name

Class name restrictions

Though no longer longer strictly enforced, the following is still best practice. Not following this could also lead to errors in the future (source). In short, follow these class naming practices: AAA_BB_cc where AAA is a typically the space this class will exist (e.g. VIEW3D) or is a common, uppercase abbreviation used across all of a single addon. BB is one of the 2 uppercase letters in the list below. Finally, cc is a lower-case name which describes this class, using underscores to separate words.

  • OT: Operators, as in the actions you make as a user and the buttons found in the interface.
  • PT: Panels, such as those sections in the properties screen, or a 3D view toolshelf tab.
  • MT: Menus, such as the dropdown that appears after clicking the file button
  • UL: UI Lists, such as the list of materials on an object or the list of particle systems applied.
  • HT: Header of a window, top bar that makes up the File, Edit, Redner.. etc window of the Info screen.
  • Property groups no specific naming convention, but PascalCaseIsEncouraged.

The “regex” way of describing this notation would be [A-Z][A-Z0-9_]*_MT_[A-Za-z0-9_]+ (source), but in more layman’s terms means a capitalized abbreviation, one of the 2-letter capitalized codes above, and then a fitting lowercase name, all joined together via underscores (preferred) or dashes.

Example usage

Say my “Smooth Models Addon” (or SMA) has an add smooth monkey operator. An appropriate name could then be class SMA_OT_smooth_monkey_add(bpy.types.Operator). A menu (dropdown/modal popup) holding the individual operators of my addon might be written as class SMA_MT_smooth_objects(bpy.types.Menu) and a panel in the tools tab of the 3D view might be  class SMA_PT_smooth_objects(bpy.types.Panel). Alternatively, Blender source code typically assigns a prefix based on the area of blender for which the operator pertains. For example, an operator affecting poses with lead with POSE_OT_.

Work around Matrix multiplication changes

If you do any multiplication of matrix objects, then make note: Recent version of Python (which Blender of course packages) implemented new notation for proper matrix multiplication.

As blender scripter, this looks like going from a * b to a @ b for any matrix multiplication. Be careful! The * multiplication syntax is still valid, but will attempt element-wise multiplication in 2.8 instead of matrix multiplication as in 2.7. I ran into some funny bugs here, since this won’t necessarily throw an error!

Want to support the same style of matrix multiplication for both 2.7 and 2.8? Well at first this seems to be a similar challenge as to adoption the field annotations mentioned in previous sections. Older python versions do not recognize the @ syntax and will raise parsing errors! This is true for all versions of blender 2.7x. One way around this is to call the matrix dot multiplication as a function instead of using the @ syntax.

import bpy
import operator # to get function names for operators like @, +, -
def matmul(a, b):
    """Perform matrix multiplication in a blender 2.7 and 2.8 safe way"""
    if hasattr(bpy.app, "version") and bpy.app.version >= (2, 80):
        return operator.matmul(a, b) # the same as writing a @ b
    else:
        return a * b

While I generally avoid branching logic based on blender versions, here it is appropriate as it is a clean break 2.7 vs 2.8.

UI element changes

Some additional minor changes which the console will again make readily evident include the change of layout.split(percentage=0.5) → layout.split(factor=0.5). If you want your UI code to work in both 2.8 and 2.8, then this utility could be helpful:

def layout_split(layout, factor=0.0, align=False):
    """Intermediate method for pre and post blender 2.8 split UI function"""
    if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80):
        return layout.split(percentage=factor, align=align)
    return layout.split(factor=factor, align=align)

Note that some developers may choose to simply have different UI files for 2.7 and 2.8, and import accordingly. If you have enough changes to make, such as utilizing multi-column layouts, that may make more sense. I do end up checking the blender version here, but while supporting super old blender versions (predating the bpy.app.version api).

Other misc changes and renaming

There many more small changes such as renamed panels, properties, operators, etc. This list below is not exhaustive, but will have at least examples I personally ran into.

In general, these one-liner changes are better suited to replace with the appropriate hasattr if-else blocks, rather than wrapping it into entire other functions (functions have more overhead than a single if/else).

  • Empty objects: obj.empty_draw_size → obj.empty_display_size
  • Empty objects: obj.empty_draw_type → obj.empty_display_type
  • Scene horizon color: scene.world.horizon_color → scene.world.color
  • Cycles sample world as light: scene.world.cycles.sample_as_light → scene.world.cycles.sampling_method, and 2.7’s True/False values are now the Enum values “MANUAL”, “AUTO” (default), and “NONE”
  • User preferences: context.user_preferences → context.preferences, see dedicated section!
  • BMesh flags changed to strings: bmesh.ops.mirror(bm, geom=verts, axis=1) → bmesh.ops.mirror(bm, geom=verts, axis=’Y’)

Additional help and closing notes

While this list of changes is not exhaustive, and may become out of date, it is a great starting point. Remember, the console is your friend! Play around with your addon and watch the console for errors or warnings. Keep in mind the principles shared here to make changes without breaking code for older Blender versions.  In a few days a follow up article will also cover best practices for maintaining addons.

In the meantime, happy updating! And check out these additional sources:

Look out for an upcoming post on the best practices for maintaining blender addons and scripts!

You may also like

Leave a Comment