Convert property definitions from assignment to annotations on Blender 2.80+
The properties are still declared in the Python 3.5 compatible assignment notation, and a class decorator that converts those to class annotations as preferred by Blender 2.80.
This commit is contained in:
parent
28f68c6fbf
commit
23b1f7de7d
@ -47,6 +47,7 @@ if "bpy" in locals():
|
||||
pillar = importlib.reload(pillar)
|
||||
async_loop = importlib.reload(async_loop)
|
||||
blender = importlib.reload(blender)
|
||||
compatibility = importlib.reload(compatibility)
|
||||
else:
|
||||
import bpy
|
||||
|
||||
@ -54,7 +55,7 @@ else:
|
||||
from . import draw_27 as draw
|
||||
else:
|
||||
from . import draw
|
||||
from .. import pillar, async_loop, blender
|
||||
from .. import pillar, async_loop, blender, compatibility
|
||||
|
||||
import bpy
|
||||
import pillarsdk
|
||||
@ -252,7 +253,7 @@ class ATTRACT_PT_tools(AttractPollMixin, Panel):
|
||||
icon='RENDER_STILL')
|
||||
|
||||
# Group more dangerous operations.
|
||||
dangerous_sub = layout.split(**blender.factor(0.6), align=True)
|
||||
dangerous_sub = layout.split(**compatibility.factor(0.6), align=True)
|
||||
dangerous_sub.operator('attract.strip_unlink',
|
||||
text='Unlink %s' % noun,
|
||||
icon='PANEL_CLOSE')
|
||||
@ -408,6 +409,7 @@ class ATTRACT_OT_shot_fetch_update(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class ATTRACT_OT_shot_relink(AttractOperatorMixin, Operator):
|
||||
bl_idname = "attract.shot_relink"
|
||||
bl_label = "Relink With Attract"
|
||||
@ -477,6 +479,7 @@ class ATTRACT_OT_shot_open_in_browser(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class ATTRACT_OT_shot_delete(AttractOperatorMixin, Operator):
|
||||
bl_idname = 'attract.shot_delete'
|
||||
bl_label = 'Delete Shot'
|
||||
@ -915,6 +918,7 @@ class ATTRACT_OT_copy_id_to_clipboard(AttractOperatorMixin, Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class ATTRACT_OT_project_open_in_browser(Operator):
|
||||
bl_idname = 'attract.project_open_in_browser'
|
||||
bl_label = 'Open Project in Browser'
|
||||
|
@ -30,7 +30,7 @@ from bpy.types import AddonPreferences, Operator, WindowManager, Scene, Property
|
||||
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty
|
||||
import rna_prop_ui
|
||||
|
||||
from . import pillar, async_loop, flamenco, project_specific
|
||||
from . import compatibility, pillar, async_loop, flamenco, project_specific
|
||||
from .utils import pyside_cache, redraw
|
||||
|
||||
PILLAR_WEB_SERVER_URL = os.environ.get('BCLOUD_SERVER', 'https://cloud.blender.org/')
|
||||
@ -40,23 +40,6 @@ ADDON_NAME = 'blender_cloud'
|
||||
log = logging.getLogger(__name__)
|
||||
icons = None
|
||||
|
||||
if bpy.app.version < (2, 80):
|
||||
SYNC_SELECT_VERSION_ICON = 'DOTSDOWN'
|
||||
else:
|
||||
SYNC_SELECT_VERSION_ICON = 'DOWNARROW_HLT'
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def factor(factor: float) -> dict:
|
||||
"""Construct keyword argument for UILayout.split().
|
||||
|
||||
On Blender 2.8 this returns {'factor': factor}, and on earlier Blenders it returns
|
||||
{'percentage': factor}.
|
||||
"""
|
||||
if bpy.app.version < (2, 80, 0):
|
||||
return {'percentage': factor}
|
||||
return {'factor': factor}
|
||||
|
||||
|
||||
@pyside_cache('version')
|
||||
def blender_syncable_versions(self, context):
|
||||
@ -69,6 +52,7 @@ def blender_syncable_versions(self, context):
|
||||
return [(v, v, '') for v in versions]
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class SyncStatusProperties(PropertyGroup):
|
||||
status = EnumProperty(
|
||||
items=[
|
||||
@ -156,6 +140,7 @@ def project_extensions(project_id) -> set:
|
||||
return set(proj.get('enabled_for', ()))
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class BlenderCloudProjectGroup(PropertyGroup):
|
||||
status = EnumProperty(
|
||||
items=[
|
||||
@ -185,6 +170,7 @@ class BlenderCloudProjectGroup(PropertyGroup):
|
||||
project_specific.handle_project_update()
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class BlenderCloudPreferences(AddonPreferences):
|
||||
bl_idname = ADDON_NAME
|
||||
|
||||
@ -331,7 +317,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
bss = context.window_manager.blender_sync_status
|
||||
bsync_box = layout.box()
|
||||
bsync_box.enabled = msg_icon != 'ERROR'
|
||||
row = bsync_box.row().split(**factor(0.33))
|
||||
row = bsync_box.row().split(**compatibility.factor(0.33))
|
||||
row.label(text='Blender Sync with Blender Cloud', icon_value=icon('CLOUD'))
|
||||
|
||||
icon_for_level = {
|
||||
@ -374,7 +360,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
layout.enabled = bss.status in {'NONE', 'IDLE'}
|
||||
|
||||
buttons = layout.column()
|
||||
row_buttons = buttons.row().split(**factor(0.5))
|
||||
row_buttons = buttons.row().split(**compatibility.factor(0.5))
|
||||
row_push = row_buttons.row()
|
||||
row_pull = row_buttons.row(align=True)
|
||||
|
||||
@ -396,7 +382,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
props.blender_version = bss.version
|
||||
row_pull.operator('pillar.sync',
|
||||
text='',
|
||||
icon=SYNC_SELECT_VERSION_ICON).action = 'SELECT'
|
||||
icon=compatibility.SYNC_SELECT_VERSION_ICON).action = 'SELECT'
|
||||
else:
|
||||
row_pull.label(text='Cloud Sync is running.')
|
||||
|
||||
@ -444,7 +430,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
header_row = flamenco_box.row(align=True)
|
||||
header_row.label(text='Flamenco:', icon_value=icon('CLOUD'))
|
||||
|
||||
manager_split = flamenco_box.split(**factor(0.32), align=True)
|
||||
manager_split = flamenco_box.split(**compatibility.factor(0.32), align=True)
|
||||
manager_split.label(text='Manager:')
|
||||
manager_box = manager_split.row(align=True)
|
||||
|
||||
@ -461,7 +447,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
else:
|
||||
manager_box.label(text='Fetching available managers.')
|
||||
|
||||
path_split = flamenco_box.split(**factor(0.32), align=True)
|
||||
path_split = flamenco_box.split(**compatibility.factor(0.32), align=True)
|
||||
path_split.label(text='Job File Path:')
|
||||
path_box = path_split.row(align=True)
|
||||
path_box.prop(self, 'flamenco_job_file_path', text='')
|
||||
@ -469,7 +455,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
props.path = self.flamenco_job_file_path
|
||||
|
||||
job_output_box = flamenco_box.column(align=True)
|
||||
path_split = job_output_box.split(**factor(0.32), align=True)
|
||||
path_split = job_output_box.split(**compatibility.factor(0.32), align=True)
|
||||
path_split.label(text='Job Output Path:')
|
||||
path_box = path_split.row(align=True)
|
||||
path_box.prop(self, 'flamenco_job_output_path', text='')
|
||||
@ -477,7 +463,7 @@ class BlenderCloudPreferences(AddonPreferences):
|
||||
props.path = self.flamenco_job_output_path
|
||||
job_output_box.prop(self, 'flamenco_exclude_filter')
|
||||
|
||||
prop_split = job_output_box.split(**factor(0.32), align=True)
|
||||
prop_split = job_output_box.split(**compatibility.factor(0.32), align=True)
|
||||
prop_split.label(text='Strip Components:')
|
||||
prop_split.prop(self, 'flamenco_job_output_strip_components', text='')
|
||||
|
||||
@ -561,6 +547,7 @@ class PILLAR_OT_subscribe(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class PILLAR_OT_project_open_in_browser(Operator):
|
||||
bl_idname = 'pillar.project_open_in_browser'
|
||||
bl_label = 'Open in Browser'
|
||||
|
70
blender_cloud/compatibility.py
Normal file
70
blender_cloud/compatibility.py
Normal file
@ -0,0 +1,70 @@
|
||||
"""Compatibility functions to support Blender 2.79 and 2.80+ in one code base."""
|
||||
import functools
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
if bpy.app.version < (2, 80):
|
||||
SYNC_SELECT_VERSION_ICON = 'DOTSDOWN'
|
||||
else:
|
||||
SYNC_SELECT_VERSION_ICON = 'DOWNARROW_HLT'
|
||||
|
||||
|
||||
# Get references to all property definition functions in bpy.props,
|
||||
# so that they can be used to replace 'x = IntProperty()' to 'x: IntProperty()'
|
||||
# dynamically when working on Blender 2.80+
|
||||
__all_prop_funcs = {
|
||||
getattr(bpy.props, propname)
|
||||
for propname in dir(bpy.props)
|
||||
if propname.endswith('Property')
|
||||
}
|
||||
|
||||
def convert_properties(class_):
|
||||
"""Class decorator to avoid warnings in Blender 2.80+
|
||||
|
||||
This decorator replaces property definitions like this:
|
||||
|
||||
someprop = bpy.props.IntProperty()
|
||||
|
||||
to annotations, as introduced in Blender 2.80:
|
||||
|
||||
someprop: bpy.props.IntProperty()
|
||||
|
||||
No-op if running on Blender 2.79 or older.
|
||||
"""
|
||||
|
||||
if bpy.app.version < (2, 80):
|
||||
return class_
|
||||
|
||||
if not hasattr(class_, '__annotations__'):
|
||||
class_.__annotations__ = {}
|
||||
|
||||
attrs_to_delete = []
|
||||
for name, value in class_.__dict__.items():
|
||||
if not isinstance(value, tuple) or len(value) != 2:
|
||||
continue
|
||||
|
||||
prop_func, kwargs = value
|
||||
if prop_func not in __all_prop_funcs:
|
||||
continue
|
||||
|
||||
# This is a property definition, replace it with annotation.
|
||||
attrs_to_delete.append(name)
|
||||
class_.__annotations__[name] = value
|
||||
|
||||
for attr_name in attrs_to_delete:
|
||||
delattr(class_, attr_name)
|
||||
|
||||
return class_
|
||||
|
||||
|
||||
@functools.lru_cache()
|
||||
def factor(factor: float) -> dict:
|
||||
"""Construct keyword argument for UILayout.split().
|
||||
|
||||
On Blender 2.8 this returns {'factor': factor}, and on earlier Blenders it returns
|
||||
{'percentage': factor}.
|
||||
"""
|
||||
if bpy.app.version < (2, 80, 0):
|
||||
return {'percentage': factor}
|
||||
return {'factor': factor}
|
@ -34,12 +34,13 @@ if "bpy" in locals():
|
||||
bat_interface = importlib.reload(bat_interface)
|
||||
sdk = importlib.reload(sdk)
|
||||
blender = importlib.reload(blender)
|
||||
compatibility = importlib.reload(compatibility)
|
||||
except NameError:
|
||||
from . import bat_interface, sdk
|
||||
from .. import blender
|
||||
from .. import blender, compatibility
|
||||
else:
|
||||
from . import bat_interface, sdk
|
||||
from .. import blender
|
||||
from .. import blender, compatibility
|
||||
|
||||
import bpy
|
||||
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
|
||||
@ -138,6 +139,7 @@ def silently_quit_blender():
|
||||
bpy.ops.wm.quit_blender()
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class FlamencoManagerGroup(PropertyGroup):
|
||||
manager = EnumProperty(
|
||||
items=available_managers,
|
||||
@ -238,6 +240,7 @@ def guess_output_file_extension(output_format: str, scene) -> str:
|
||||
return '.' + container.lower()
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
|
||||
pillar.AuthenticatedPillarOperatorMixin,
|
||||
FlamencoPollMixin,
|
||||
@ -729,6 +732,7 @@ class FLAMENCO_OT_abort(Operator, FlamencoPollMixin):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class FLAMENCO_OT_explore_file_path(FlamencoPollMixin,
|
||||
Operator):
|
||||
"""Opens the Flamenco job storage path in a file explorer.
|
||||
@ -796,6 +800,7 @@ class FLAMENCO_OT_disable_output_path_override(Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class FLAMENCO_OT_set_recommended_sample_cap(Operator):
|
||||
bl_idname = 'flamenco.set_recommended_sample_cap'
|
||||
bl_label = 'Set Recommended Maximum Sample Count'
|
||||
@ -962,7 +967,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
|
||||
prefs = preferences()
|
||||
|
||||
labeled_row = layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Manager:')
|
||||
prop_btn_row = labeled_row.row(align=True)
|
||||
|
||||
@ -980,7 +985,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
else:
|
||||
prop_btn_row.label(text='Fetching available managers.')
|
||||
|
||||
labeled_row = layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Job Type:')
|
||||
labeled_row.prop(context.scene, 'flamenco_render_job_type', text='')
|
||||
|
||||
@ -1005,7 +1010,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
sample_count = scene_sample_count(context.scene)
|
||||
recommended_cap = sample_count // 4
|
||||
|
||||
split = box.split(**blender.factor(0.4))
|
||||
split = box.split(**compatibility.factor(0.4))
|
||||
split.label(text='Total Sample Count: %d' % sample_count)
|
||||
props = split.operator('flamenco.set_recommended_sample_cap',
|
||||
text='Recommended Max Samples per Task: %d' % recommended_cap)
|
||||
@ -1019,7 +1024,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
else:
|
||||
box.prop(context.scene, 'flamenco_render_fchunk_size')
|
||||
|
||||
labeled_row = layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Frame Range:')
|
||||
prop_btn_row = labeled_row.row(align=True)
|
||||
prop_btn_row.prop(context.scene, 'flamenco_render_frame_range', text='')
|
||||
@ -1030,7 +1035,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
|
||||
paths_layout = layout.column(align=True)
|
||||
|
||||
labeled_row = paths_layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = paths_layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Storage:')
|
||||
prop_btn_row = labeled_row.row(align=True)
|
||||
prop_btn_row.label(text=prefs.flamenco_job_file_path)
|
||||
@ -1043,7 +1048,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
paths_layout.label(text='Unable to render with Flamenco, outside of project directory.')
|
||||
return
|
||||
|
||||
labeled_row = paths_layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = paths_layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Output:')
|
||||
prop_btn_row = labeled_row.row(align=True)
|
||||
|
||||
@ -1062,7 +1067,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
props.path = str(render_output.parent)
|
||||
|
||||
if context.scene.flamenco_do_override_output_path:
|
||||
labeled_row = paths_layout.split(**blender.factor(0.25), align=True)
|
||||
labeled_row = paths_layout.split(**compatibility.factor(0.25), align=True)
|
||||
labeled_row.label(text='Effective Output Path:')
|
||||
labeled_row.label(text=str(render_output))
|
||||
|
||||
@ -1072,7 +1077,7 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
|
||||
flamenco_status = context.window_manager.flamenco_status
|
||||
if flamenco_status in {'IDLE', 'ABORTED', 'DONE'}:
|
||||
if prefs.flamenco_show_quit_after_submit_button:
|
||||
ui = layout.split(**blender.factor(0.75), align=True)
|
||||
ui = layout.split(**compatibility.factor(0.75), align=True)
|
||||
else:
|
||||
ui = layout
|
||||
ui.operator(FLAMENCO_OT_render.bl_idname,
|
||||
|
@ -25,7 +25,7 @@ import bpy
|
||||
import pillarsdk
|
||||
from pillarsdk import exceptions as sdk_exceptions
|
||||
from .pillar import pillar_call
|
||||
from . import async_loop, pillar, home_project, blender
|
||||
from . import async_loop, compatibility, pillar, home_project, blender
|
||||
|
||||
REQUIRES_ROLES_FOR_IMAGE_SHARING = {'subscriber', 'demo'}
|
||||
IMAGE_SHARING_GROUP_NODE_NAME = 'Image sharing'
|
||||
@ -53,6 +53,7 @@ async def find_image_sharing_group_id(home_project_id, user_id):
|
||||
return share_group['_id']
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class PILLAR_OT_image_share(pillar.PillarOperatorMixin,
|
||||
async_loop.AsyncModalOperatorMixin,
|
||||
bpy.types.Operator):
|
||||
|
@ -35,7 +35,7 @@ import asyncio
|
||||
import pillarsdk
|
||||
from pillarsdk import exceptions as sdk_exceptions
|
||||
from .pillar import pillar_call
|
||||
from . import async_loop, blender, pillar, cache, blendfile, home_project
|
||||
from . import async_loop, blender, compatibility, pillar, cache, blendfile, home_project
|
||||
|
||||
SETTINGS_FILES_TO_UPLOAD = ['userpref.blend', 'startup.blend']
|
||||
|
||||
@ -196,6 +196,7 @@ async def available_blender_versions(home_project_id: str, user_id: str) -> list
|
||||
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
@compatibility.convert_properties
|
||||
class PILLAR_OT_sync(pillar.PillarOperatorMixin,
|
||||
async_loop.AsyncModalOperatorMixin,
|
||||
bpy.types.Operator):
|
||||
|
@ -26,7 +26,7 @@ import bpy
|
||||
import bgl
|
||||
|
||||
import pillarsdk
|
||||
from .. import async_loop, pillar, cache, blender, utils
|
||||
from .. import async_loop, compatibility, pillar, cache, blender, utils
|
||||
from . import menu_item as menu_item_mod # so that we can have menu items called 'menu_item'
|
||||
from . import nodes
|
||||
|
||||
@ -648,6 +648,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
|
||||
self.scroll_offset_target = self.scroll_offset = 0
|
||||
|
||||
|
||||
@compatibility.convert_properties
|
||||
class PILLAR_OT_switch_hdri(pillar.PillarOperatorMixin,
|
||||
async_loop.AsyncModalOperatorMixin,
|
||||
bpy.types.Operator):
|
||||
@ -776,7 +777,7 @@ def _hdri_download_panel(self, current_image):
|
||||
current_image.name)
|
||||
return
|
||||
|
||||
row = self.layout.row(align=True).split(**blender.factor(0.3))
|
||||
row = self.layout.row(align=True).split(**compatibility.factor(0.3))
|
||||
row.label(text='HDRi', icon_value=blender.icon('CLOUD'))
|
||||
row.prop(current_image, 'hdri_variation', text='')
|
||||
|
||||
|
@ -94,7 +94,10 @@ def pyside_cache(propname):
|
||||
result = wrapped(self, context)
|
||||
return result
|
||||
finally:
|
||||
rna_type, rna_info = getattr(self.bl_rna, propname)
|
||||
try:
|
||||
rna_type, rna_info = self.bl_rna.__annotations__[propname]
|
||||
except AttributeError:
|
||||
rna_type, rna_info = getattr(self.bl_rna, propname)
|
||||
rna_info['_cached_result'] = result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
Reference in New Issue
Block a user