3
$\begingroup$

We distribute a Blender Addon that is prepared to work on multiple Blender Versions (we do not want to force our customers to update the Addon whenever they update Blender). But occasionally we add a feature that can only work on newer versions of Blender. So we inspect Blender and enable the related function (and its UI Button) only if it works in Blender.

However we know that our customers rarely find the time to read news about feature changes. So instead of silently disabling a feature when it can not run, we want to inform in the tooltips that updating Blender would enable the feature.

Here is how we do this for a Helper tool that provides Normals welding. This tool needs Blender 2.75 at least, and can not run on earlier versions of Blender:

class WeldNormals(bpy.types.Operator):
    has_normals = 'normals_split_custom_set' in bpy.types.Mesh.bl_rna.functions
    if has_normals:
        remark = "only operates on boundary edges"
    else:
        remark = "needs Blender 2.75 or newer"

    bl_description = "Weld Normals to adjacent objects (%s)" % (remark)
    bl_idname      = "sparkles.weld_normals"
    bl_label       = "Weld Normals"
    bl_options     = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(self, context):
        ob = context.active_object
        return ob and ob.type == 'MESH' and self.has_normals

        ...

While this works, i am not sure if this is the most elegant way to do it. Is there any better solution available for this?

$\endgroup$
3
$\begingroup$

There isn't really a right/best way to do this. It depends on what kind of control you want.

Personally, I would go for the least intrusive option where possible, so code remains readable, and when you finally drop support for older versions it can be removed without messing up your code history (so code annotations remain useful, eg git blame).

class bpy_support:
    """Store all supported features here"""
    has_normals = ('normals_split_custom_set' in bpy.types.Mesh.bl_rna.functions)
    has_other_feature = (...)

...    

class WeldNormals(bpy.types.Operator):
    """Weld Normals to adjacent objects """ \
    """(only operates on boundary edges)"""
    bl_idname      = "sparkles.weld_normals"
    bl_label       = "Weld Normals"
    bl_options     = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(self, context):
        ob = context.active_object
        return ob and ob.type == 'MESH'

    ...

    # Remove this block when 2.74x support is dropped!
    if not bpy_support.has_normals:
        __doc__ = "Unsupported, upgrade to Blender 2.75 or newer! " + __doc__

        @classmethod
        def poll(self, context):
            return False            

This way exceptions are isolated and can be cleanly removed when they're no longer needed.

The trade-off of using a simple override is you can't mix in logic so easily.


If you were to do this for more then a few classes, you could make a utility function for this which overrides poll and the tool-tip.

# run before registration
operator_requires(WeldNormals, version_atleast=(2, 75))
operator_requires(AnotherOperator, features={'has_normals', 'has_other_feature'})
operator_requires(ThirdOperator, depends={WeldNormals, AnotherOperator})

...

def register():
    ...
$\endgroup$
1
$\begingroup$

Not sure this is either more elegant... or better, but I find it useful. You can create a class from a dictionary using the type(...) method.

from bpy.types import Operator

@classmethod 
def nopoll(self, context):
    return False

@classmethod
def poll(self, context):
    return context.active_object is not None

def OpVersion(version):
    prop_dic = {"bl_description": "Version %s" % (str(version)),
                "poll": poll if version >= 2.75 else nopoll,
                "bl_idname": "sparkles.merger",
                "bl_label": "Some Label",
                }

    return type("OpVersion", (), prop_dic)
    # could also just use 
    # Opversion = type(name, (), prop_dic)


v = bpy.app.version[0] + bpy.app.version[1] / 100

class SparkleOperator(OpVersion(v), Operator):
    pass
#equiv
#SparkleOperator = type("SparkleOperator", (OpVersion(v), Operator,) {})
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.