Nice UI and proper refreshing versions & loading settings.

This commit is contained in:
2016-06-24 12:53:49 +02:00
parent 671e9f31fa
commit e73e9d3df7
3 changed files with 126 additions and 58 deletions

View File

@@ -67,8 +67,9 @@ def register():
gui = reload_mod('gui')
async_loop = reload_mod('async_loop')
settings_sync = reload_mod('settings_sync')
reload_mod('blendfile')
else:
from . import blender, gui, async_loop, settings_sync
from . import blender, gui, async_loop, settings_sync, blendfile
async_loop.setup_asyncio_executor()
async_loop.register()

View File

@@ -19,10 +19,17 @@ log = logging.getLogger(__name__)
def redraw(self, context):
log.debug('SyncStatusProperties.status = %s', self.status)
context.area.tag_redraw()
def blender_syncable_versions(self, context):
bss = context.window_manager.blender_sync_status
versions = bss.available_blender_versions
if not versions:
return [('', 'No settings stored in your home project.', '')]
return [(v, v, '') for v in versions]
class SyncStatusProperties(PropertyGroup):
status = EnumProperty(
items=[
@@ -33,6 +40,12 @@ class SyncStatusProperties(PropertyGroup):
name='status',
description='Current status of Blender Sync.',
update=redraw)
version = EnumProperty(
items=blender_syncable_versions,
name='Version of Blender from which to pull',
description='Version of Blender from which to pull')
message = StringProperty(name='message', update=redraw)
level = EnumProperty(
items=[
@@ -47,7 +60,21 @@ class SyncStatusProperties(PropertyGroup):
assert len(level) == 1, 'level should be a set of one string, not %r' % level
self.level = level.pop()
self.message = message
log.error('REPORT %s: %s / %s', self, self.level, self.message)
# Message can also be empty, just to erase it from the GUI.
# No need to actually log those.
if message:
log.log(logging._nameToLevel[self.level], message)
# List of syncable versions is stored in 'available_blender_versions' ID property,
# because I don't know how to store a variable list of strings in a proper RNA property.
@property
def available_blender_versions(self) -> list:
return self.get('available_blender_versions', [])
@available_blender_versions.setter
def available_blender_versions(self, new_versions):
self['available_blender_versions'] = new_versions
class BlenderCloudPreferences(AddonPreferences):
@@ -101,31 +128,28 @@ class BlenderCloudPreferences(AddonPreferences):
help_text = ('To logout or change profile, '
'go to the Blender ID add-on preferences.')
sub = layout.column(align=True)
sub.label(text=text, icon=icon)
# Authentication stuff
auth_box = layout.box()
auth_box.label(text=text, icon=icon)
help_lines = textwrap.wrap(help_text, 80)
for line in help_lines:
sub.label(text=line)
auth_box.label(text=line)
auth_box.operator("pillar.credentials_update")
sub = layout.column()
# Texture browser stuff
texture_box = layout.box()
texture_box.enabled = icon != 'ERROR'
sub = texture_box.column()
sub.label(text='Local directory for downloaded textures')
sub.prop(self, "local_texture_dir", text='Default')
sub.prop(context.scene, "local_texture_dir", text='Current scene')
# options for Pillar
sub = layout.column()
sub.enabled = icon != 'ERROR'
# TODO: let users easily pick a project. For now, we just use the
# hard-coded server URL and UUID of the textures project.
# sub.prop(self, "pillar_server")
# sub.prop(self, "project_uuid")
sub.operator("pillar.credentials_update")
# Blender Sync stuff
bss = context.window_manager.blender_sync_status
col = layout.column()
row = col.row()
bsync_box = layout.box()
bsync_box.enabled = icon != 'ERROR'
row = bsync_box.row().split(percentage=0.33)
row.label('Blender Sync')
icon_for_level = {
@@ -134,25 +158,39 @@ class BlenderCloudPreferences(AddonPreferences):
'ERROR': 'ERROR',
}
message_container = row.row()
message_container.label(bss.message or '-idle-', icon=icon_for_level[bss.level])
# message_container.enabled = bool(bss.message)
message_container.label(bss.message, icon=icon_for_level[bss.level])
message_container.alert = True # bss.level in {'WARNING', 'ERROR'}
sub = col.column()
sub = bsync_box.column()
sub.enabled = bss.status in {'NONE', 'IDLE'}
row = sub.row()
row.operator('pillar.sync', text='Refresh', icon='FILE_REFRESH').action = 'REFRESH'
row.operator('pillar.sync', text='To Cloud').action = 'PUSH'
buttons = sub.column()
row_buttons = buttons.row().split(percentage=0.5)
row_pull = row_buttons.row(align=True)
row_push = row_buttons.row()
if 'available_blender_versions' in bss:
for version in bss['available_blender_versions']:
props = sub.operator('pillar.sync', icon='FILE_REFRESH',
text='From Cloud %s' % version)
row_push.operator('pillar.sync',
text='Save %i.%i settings to Cloud' % bpy.app.version[:2],
icon='TRIA_UP').action = 'PUSH'
versions = bss.available_blender_versions
version = bss.version
if bss.status in {'NONE', 'IDLE'}:
if not versions or not version:
row_pull.operator('pillar.sync',
text='Find version to load from Cloud',
icon='TRIA_DOWN').action = 'REFRESH'
else:
props = row_pull.operator('pillar.sync',
text='Load %s settings from Cloud' % version,
icon='TRIA_DOWN')
props.action = 'PULL'
props.blender_version = version
# sub.prop(bss, 'level')
row_pull.operator('pillar.sync',
text='',
icon='DOTSDOWN').action = 'SELECT'
else:
row_pull.label('Cloud Sync is running.')
class PillarCredentialsUpdate(Operator):

View File

@@ -1,4 +1,3 @@
"""Synchronises settings & startup file with the Cloud.
Caching is disabled on many PillarSDK calls, as synchronisation can happen
rapidly between multiple machines. This means that information can be outdated
@@ -66,6 +65,7 @@ def async_set_blender_sync_status(set_status: str):
bss.status = 'IDLE'
return wrapper
return decorator
@@ -240,7 +240,6 @@ async def available_blender_versions(home_project_id: str, user_id: str) -> list
if sync_group is None:
bss.report({'ERROR'}, 'No synced Blender settings in your home project')
log.warning('No synced Blender settings in your home project')
log.debug('-- unable to find sync group for home_project_id=%r and user_id=%r',
home_project_id, user_id)
return []
@@ -259,13 +258,12 @@ async def available_blender_versions(home_project_id: str, user_id: str) -> list
if not sync_nodes or not sync_nodes._items:
bss.report({'ERROR'}, 'No synced Blender settings in your home project')
log.warning('No synced Blender settings in your home project')
return []
versions = sync_nodes._items
log.info('Versions: %s', versions)
versions = [node.name for node in sync_nodes._items]
log.debug('Versions: %s', versions)
return [node.name for node in versions]
return versions
# noinspection PyAttributeOutsideInit
@@ -285,6 +283,7 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
('PUSH', 'Push', 'Push settings to the Blender Cloud'),
('PULL', 'Pull', 'Pull settings from the Blender Cloud'),
('REFRESH', 'Refresh', 'Refresh available versions'),
('SELECT', 'Select', 'Select version to sync'),
],
name='action')
@@ -297,10 +296,11 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
bss.report(level, message)
def invoke(self, context, event):
self.log.info('at invoke: self = %r', self)
if self.action == 'SELECT':
# Synchronous action
return self.action_select(context)
self.log.info('Pulling from Blender %s', self.blender_version)
if not self.blender_version:
if self.action in {'PUSH', 'PULL'} and not self.blender_version:
self.bss_report({'ERROR'}, 'No Blender version to sync for was given.')
return {'CANCELLED'}
@@ -310,11 +310,47 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
self._new_async_task(self.async_execute(context))
return {'RUNNING_MODAL'}
def action_select(self, context):
"""Allows selection of the Blender version to use.
This is a synchronous action, as it requires a dialog box.
"""
self.log.info('Performing action SELECT')
# Do a refresh before we can show the dropdown.
fut = asyncio.ensure_future(self.async_execute(context, action_override='REFRESH'))
loop = asyncio.get_event_loop()
loop.run_until_complete(fut)
self._state = 'SELECTING'
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
bss = bpy.context.window_manager.blender_sync_status
self.layout.prop(bss, 'version')
def execute(self, context):
if self.action != 'SELECT':
log.debug('Ignoring execute() for action %r', self.action)
return {'FINISHED'}
log.debug('Performing execute() for action %r', self.action)
# Perform the sync when the user closes the dialog box.
bss = bpy.context.window_manager.blender_sync_status
bpy.ops.pillar.sync('INVOKE_DEFAULT',
action='PULL',
blender_version=bss.version)
return {'FINISHED'}
@async_set_blender_sync_status('SYNCING')
async def async_execute(self, context):
async def async_execute(self, context, *, action_override=None):
"""Entry point of the asynchronous operator."""
self.bss_report({'INFO'}, 'Synchronizing settings %s with Blender Cloud' % self.action)
action = action_override or self.action
self.bss_report({'INFO'}, 'Communicating with Blender Cloud')
self.log.info('Performing action %s', action)
try:
self.user_id = await self.check_credentials(context)
@@ -335,9 +371,9 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
may_create=may_create)
self.sync_group_id = gid
self.sync_group_versioned_id = subgid
self.log.info('Found top-level group node ID: %s', self.sync_group_id)
self.log.info('Found group node ID for %s: %s',
self.blender_version, self.sync_group_versioned_id)
self.log.debug('Found top-level group node ID: %s', self.sync_group_id)
self.log.debug('Found group node ID for %s: %s',
self.blender_version, self.sync_group_versioned_id)
except sdk_exceptions.ForbiddenAccess:
self.log.exception('Unable to find Group ID')
self.bss_report({'ERROR'}, 'Unable to find sync folder.')
@@ -345,12 +381,12 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
return
# Perform the requested action.
action = {
action_method = {
'PUSH': self.action_push,
'PULL': self.action_pull,
'REFRESH': self.action_refresh,
}[self.action]
await action(context)
}[action]
await action_method(context)
except Exception as ex:
self.log.exception('Unexpected exception caught.')
self.bss_report({'ERROR'}, 'Unexpected error: %s' % ex)
@@ -378,17 +414,12 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
self.user_id,
future=self.signalling_future)
await self.action_refresh(context)
self.bss_report({'INFO'}, 'Settings pushed to Blender Cloud.')
async def action_pull(self, context):
"""Loads files from the Pillar server."""
# Refuse to start if the file hasn't been saved.
if context.blend_data.is_dirty:
self.bss_report({'ERROR'}, 'Please save your Blend file before pulling'
' settings from the Blender Cloud.')
return
# If the sync group node doesn't exist, offer a list of groups that do.
if self.sync_group_id is None:
self.bss_report({'ERROR'}, 'There are no synced Blender settings in your home project.')
@@ -396,7 +427,7 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
if self.sync_group_versioned_id is None:
self.bss_report({'ERROR'}, 'Therre are no synced Blender settings for version %s' %
self.blender_version)
self.blender_version)
return
self.bss_report({'INFO'}, 'Pulling settings from Blender Cloud')
@@ -405,7 +436,6 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
await self.download_settings_file(fname, tempdir)
self.bss_report({'WARNING'}, 'Settings pulled from Cloud, restart Blender to load them.')
self.log.info('at end: self = %r', self)
async def action_refresh(self, context):
self.bss_report({'INFO'}, 'Refreshing available Blender versions.')
@@ -416,11 +446,10 @@ class PILLAR_OT_sync(pillar.PillarOperatorMixin,
versions = await available_blender_versions(self.home_project_id, self.user_id)
bss = bpy.context.window_manager.blender_sync_status
bss['available_blender_versions'] = versions
bss.available_blender_versions = versions
self.bss_report({'INFO'}, '')
async def download_settings_file(self, fname: str, temp_dir: str):
config_dir = pathlib.Path(bpy.utils.user_resource('CONFIG'))
meta_path = cache.cache_directory('home-project', 'blender-sync')