7
$\begingroup$

I know this works:

import bpy

class Test(bpy.types.PropertyGroup):
    def unedit(self, context):
        print(self.type)
    if bpy.app.version >= (2, 80, 0):
        type: bpy.props.StringProperty(default='xxxxxxxxxxxxxxxxxx')
        name: bpy.props.StringProperty(update=unedit)
    else:
        type= bpy.props.StringProperty(default='xxxxxxxxxxxxxxxxxx')
        name= bpy.props.StringProperty(update=unedit)

bpy.utils.register_class(Test)

but it is coping and paste solution (bed for many properties). Is there a better way?

$\endgroup$
6
$\begingroup$

Two separate addons

The code in question and your answer has versioning for one part of the changes from 2.79 to 2.80, ie annotations in property groups.

I would suggest writing two addons, one that works in 2.79 and one that works in 2.80. (As opposed to having versioning code ad hoc in each py file.)

Distribute as an addon for that version. If using github a branch is often used.

If wish to distribute as a single addon that works in both, suggest putting each in a subfolder of the root folder of the addon, then in the addons __init__.py check once for version and import modules from that version folder.

See this post on fake modules for some cool trickery https://stackoverflow.com/a/27476659/5317130

$\endgroup$
  • 2
    $\begingroup$ I think this approach is best since many other changes exist in 2.8 that have to be fixed for a 2.79 script to work there. $\endgroup$ – Craig D Jones Mar 4 '19 at 13:23
  • 1
    $\begingroup$ That also makes it easier to discontinue/freeze the development 2.79 addon, since the Blender 2.8x code is probably going to differ more and more due to further development. $\endgroup$ – metaphor_set Mar 5 '19 at 5:54
3
$\begingroup$

The problem I have with making a code that works in both versions is it gets more complicated and more evaluation needs to happen at runtime. There is extra code and 'baggage' than in either version would be.

Normally in c/c++ you would implement pre-processor directives into the code to organize it and generate an executable of only what you need.

In python this is not so easy. You can use __debug__ which is pre-processed by the compiler and evaluated based on -O command line argument and any if False: part of code will be skipped by the optimizer, but you have to run the code with arguments which is not convenient for Blender use.

The solution I found is to use pypreprocessor. It lets you do:

from pypreprocessor import pypreprocessor
pypreprocessor.parse()

#define 2_80

#ifdef 2_80
print('2_80 code')
#else
print('2_79 code')
#endif

Normally when you call .parse() it generates a temporary copy with pypreprocessor commented (not to run recursively) and also any unused code commented out and this post-processed code is executed on-the-fly.

The best thing about it is instead of executing the post-processed code you can output to a file with a user-defined filename and even remove all the preprocessor directives and preprocessor specific code:

pypreprocessor.defines.append('define')
pypreprocessor.run = False
pypreprocessor.save = True
pypreprocessor.output = 'addon_2_80.py'
pypreprocessor.removeMeta = True

This way you get clean files for 2.80 or 2.79 versions, that no one knows were generated from the same source.

$\endgroup$
2
$\begingroup$

I am answering my own question, maybe someone finds it useful.

import bpy

class Test_2_80(bpy.types.PropertyGroup):
    def unedit(self, context):
        print(self.type)
    type: bpy.props.StringProperty(default='xxxxxxxxxxxxxxxxxx')
    name: bpy.props.StringProperty(update=unedit)


class Test(Test_2_80):
    if bpy.app.version < (2, 80, 0):
        for val, fun in Test_2_80.__annotations__.items():
            exec('{} = fun'.format(val))
        val, fun = None, None
    else:
        pass

bpy.utils.register_class(Test)

bpy.types.Scene.test = bpy.props.PointerProperty(type=Test)
bpy.context.scene.test.name = 'g'

Any ideas for better solutions?

$\endgroup$
2
$\begingroup$

You can just use the assignment and accept that it'll show warnings on 2.80. It'll still work, and you can make the step to 2.80+ when 2.80 or 2.81 is released.

$\endgroup$
2
$\begingroup$

Note: I have a lot of experience with Python, but no experience with Blender (I found the source code to look through).

Warning: I also have not tested either.

Another option is to create a decorator:

import bpy

def copy_annotations_to_dict_if_old_blender(x):
    if bpy.app.version < (2, 80):
        for name, value in x.__annotations__.items():  # x.__dict__.update doesn't work
            setattr(x, name, value)

    return x

@copy_annotations_to_dict_if_old_blender
class Test(bpy.types.PropertyGroup):
    def unedit(self, context):
        print(self.type)
    type: bpy.props.StringProperty(default='xxxxxxxxxxxxxxxxxx')
    name: bpy.props.StringProperty(update=unedit)

Previous code that used a metaclass instead:

import bpy

if bpy.app.version >= (2, 80):
    MyPropertyGroupMeta = bpy.RNAMetaPropGroup
else:
    class MyPropertyGroup(bpy.types.PropertyGroup):
        def __new__(cls, name, bases, namespace, **kwargs):
            result = super().__new__(cls, name, bases, namespace, **kwargs)
            result.__dict__.update(result.__annotations__)
            return result

class MyPropertyGroup(bpy.types.StructRNA, metaclass=MyPropertyGroupMeta):
    __slots__ = ()

class Test(MyPropertyGroup):
    def unedit(self, context):
        print(self.type)

    type: bpy.props.StringProperty(default='xxxxxxxxxxxxxxxxxx')
    name: bpy.props.StringProperty(update=unedit)

(the decorator code is more readable and more likely to work)

$\endgroup$
  • 1
    $\begingroup$ Wery pythonic but: AttributeError: 'mappingproxy' object has no attribute 'update' $\endgroup$ – Piotr Kowalczyk Mar 6 '19 at 8:55
  • $\begingroup$ @PiotrKowalczyk Does this work? The one problem is that it might be immutable. There are workarounds, but they could cause issues if caching or something similar is used. $\endgroup$ – Solomon Ucko Mar 6 '19 at 11:29
  • $\begingroup$ @PiotrKowalczyk Great! Glad I could help! $\endgroup$ – Solomon Ucko Mar 6 '19 at 13:58
  • $\begingroup$ This doesn't work, because Blender 2.79 is using Python 3.5, which doesn't support the syntax for annotations on class members. $\endgroup$ – dr. Sybren Oct 10 '19 at 7:44

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.