Nice UI and proper refreshing versions & loading settings.
This commit is contained in:
@@ -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()
|
||||
|
@@ -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):
|
||||
|
@@ -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')
|
||||
|
Reference in New Issue
Block a user