From ba14c33b6d121e227125eba7143632809cd792bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Tue, 2 Jan 2018 16:42:37 +0100 Subject: [PATCH] Store project-specific settings in the preferences. This stores project-specific settings, such as filesystem paths, for each project, and restores those settings when the project is selected again. Does not touch settings that haven't been set for the newly selected project. --- blender_cloud/__init__.py | 5 +- blender_cloud/blender.py | 52 +++++---------- blender_cloud/flamenco/__init__.py | 6 +- blender_cloud/project_specific.py | 101 +++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 38 deletions(-) create mode 100644 blender_cloud/project_specific.py diff --git a/blender_cloud/__init__.py b/blender_cloud/__init__.py index 1c69962..4b90577 100644 --- a/blender_cloud/__init__.py +++ b/blender_cloud/__init__.py @@ -88,9 +88,10 @@ def register(): settings_sync = reload_mod('settings_sync') image_sharing = reload_mod('image_sharing') blender = reload_mod('blender') + project_specific = reload_mod('project_specific') else: from . import (blender, texture_browser, async_loop, settings_sync, blendfile, home_project, - image_sharing, attract, flamenco) + image_sharing, attract, flamenco, project_specific) async_loop.setup_asyncio_executor() async_loop.register() @@ -102,7 +103,7 @@ def register(): image_sharing.register() blender.register() - blender.handle_project_update() + project_specific.handle_project_update() def _monkey_patch_requests(): diff --git a/blender_cloud/blender.py b/blender_cloud/blender.py index c6d3a75..8781ee5 100644 --- a/blender_cloud/blender.py +++ b/blender_cloud/blender.py @@ -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 +from . import 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/') @@ -38,7 +38,6 @@ PILLAR_SERVER_URL = '%sapi/' % PILLAR_WEB_SERVER_URL ADDON_NAME = 'blender_cloud' log = logging.getLogger(__name__) - icons = None @@ -140,30 +139,6 @@ def project_extensions(project_id) -> set: return set(proj.get('enabled_for', ())) -def handle_project_update(_=None, _2=None): - """Handles changing projects, which may cause extensions to be disabled/enabled. - - Ignores arguments so that it can be used as property update callback. - """ - - project_id = preferences().project.project - log.info('Updating internal state to reflect extensions enabled on current project %s.', - project_id) - - project_extensions.cache_clear() - - from blender_cloud import attract, flamenco - attract.deactivate() - flamenco.deactivate() - - enabled_for = project_extensions(project_id) - log.info('Project extensions: %s', enabled_for) - if 'attract' in enabled_for: - attract.activate() - if 'flamenco' in enabled_for: - flamenco.activate() - - class BlenderCloudProjectGroup(PropertyGroup): status = EnumProperty( items=[ @@ -178,7 +153,7 @@ class BlenderCloudProjectGroup(PropertyGroup): items=bcloud_available_projects, name='Cloud project', description='Which Blender Cloud project to work with', - update=handle_project_update + update=project_specific.handle_project_update ) # List of projects is stored in 'available_projects' ID property, @@ -190,13 +165,13 @@ class BlenderCloudProjectGroup(PropertyGroup): @available_projects.setter def available_projects(self, new_projects): self['available_projects'] = new_projects - handle_project_update() + project_specific.handle_project_update() class BlenderCloudPreferences(AddonPreferences): bl_idname = ADDON_NAME - # The following two properties are read-only to limit the scope of the + # The following property is read-only to limit the scope of the # addon and allow for proper testing within this scope. pillar_server = StringProperty( name='Blender Cloud Server', @@ -225,24 +200,32 @@ class BlenderCloudPreferences(AddonPreferences): description='Local path of your Attract project, used to search for blend files; ' 'usually best to set to an absolute path', subtype='DIR_PATH', - default='//../') + default='//../', + update=project_specific.store, + ) flamenco_manager = PointerProperty(type=flamenco.FlamencoManagerGroup) flamenco_exclude_filter = StringProperty( name='File Exclude Filter', description='Filter like "*.abc;*.mkv" to prevent certain files to be packed ' 'into the output directory', - default='') + default='', + update=project_specific.store, + ) flamenco_job_file_path = StringProperty( name='Job Storage Path', description='Path where to store job files, should be accesible for Workers too', subtype='DIR_PATH', - default=tempfile.gettempdir()) + default=tempfile.gettempdir(), + update=project_specific.store, + ) flamenco_job_output_path = StringProperty( name='Job Output Path', description='Path where to store output files, should be accessible for Workers', subtype='DIR_PATH', - default=tempfile.gettempdir()) + default=tempfile.gettempdir(), + update=project_specific.store, + ) flamenco_job_output_strip_components = IntProperty( name='Job Output Path Strip Components', description='The final output path comprises of the job output path, and the blend file ' @@ -251,11 +234,12 @@ class BlenderCloudPreferences(AddonPreferences): min=0, default=0, soft_max=4, + update=project_specific.store, ) flamenco_open_browser_after_submit = BoolProperty( name='Open Browser after Submitting Job', description='When enabled, Blender will open a webbrowser', - default=True + default=True, ) def draw(self, context): diff --git a/blender_cloud/flamenco/__init__.py b/blender_cloud/flamenco/__init__.py index 164169b..5f492ac 100644 --- a/blender_cloud/flamenco/__init__.py +++ b/blender_cloud/flamenco/__init__.py @@ -42,7 +42,7 @@ import bpy from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty -from .. import async_loop, pillar +from .. import async_loop, pillar, project_specific from ..utils import pyside_cache, redraw log = logging.getLogger(__name__) @@ -67,7 +67,9 @@ class FlamencoManagerGroup(PropertyGroup): manager = EnumProperty( items=available_managers, name='Flamenco Manager', - description='Which Flamenco Manager to use for jobs') + description='Which Flamenco Manager to use for jobs', + update=project_specific.store, + ) status = EnumProperty( items=[ diff --git a/blender_cloud/project_specific.py b/blender_cloud/project_specific.py new file mode 100644 index 0000000..af523e6 --- /dev/null +++ b/blender_cloud/project_specific.py @@ -0,0 +1,101 @@ +"""Handle saving and loading project-specific settings.""" + +import logging + +# Names of BlenderCloudPreferences properties that are both project-specific +# and simple enough to store directly in a dict. +PROJECT_SPECIFIC_SIMPLE_PROPS = ( + 'cloud_project_local_path', + 'flamenco_exclude_filter', + 'flamenco_job_file_path', + 'flamenco_job_output_path', + 'flamenco_job_output_strip_components' +) + +log = logging.getLogger(__name__) +project_settings_loading = False + + +def handle_project_update(_=None, _2=None): + """Handles changing projects, which may cause extensions to be disabled/enabled. + + Ignores arguments so that it can be used as property update callback. + """ + + from .blender import preferences, project_extensions + + global project_settings_loading + project_settings_loading = True + try: + prefs = preferences() + project_id = prefs.project.project + log.info('Updating internal state to reflect extensions enabled on current project %s.', + project_id) + + project_extensions.cache_clear() + + from blender_cloud import attract, flamenco + attract.deactivate() + flamenco.deactivate() + + enabled_for = project_extensions(project_id) + log.info('Project extensions: %s', enabled_for) + if 'attract' in enabled_for: + attract.activate() + if 'flamenco' in enabled_for: + flamenco.activate() + + # Load project-specific settings from the last time we visited this project. + ps = prefs.get('project_settings', {}).get(project_id, {}) + if not ps: + log.debug('no project-specific settings are available, not touching options') + return + + if log.isEnabledFor(logging.DEBUG): + from pprint import pformat + log.debug('loading project-specific settings:\n%s', pformat(ps.to_dict())) + + for name in PROJECT_SPECIFIC_SIMPLE_PROPS: + if name in ps and hasattr(prefs, name): + setattr(prefs, name, ps[name]) + if ps.get('flamenco_manager'): + prefs.flamenco_manager.manager = ps['flamenco_manager'] + log.debug('setting flamenco manager to %s', ps['flamenco_manager']) + + finally: + project_settings_loading = False + + +def store(_=None, _2=None): + """Remember project-specific settings as soon as one of them changes. + + Ignores arguments so that it can be used as property update callback. + + No-op when project_settings_loading=True, to prevent saving project- + specific settings while they are actually being loaded. + """ + from .blender import preferences + + global project_settings_loading + if project_settings_loading: + return + + prefs = preferences() + project_id = prefs.project.project + all_settings = prefs.get('project_settings', {}) + ps = all_settings.get(project_id, {}) + + for name in PROJECT_SPECIFIC_SIMPLE_PROPS: + ps[name] = getattr(prefs, name) + ps['flamenco_manager'] = prefs.flamenco_manager.manager + + if log.isEnabledFor(logging.DEBUG): + from pprint import pformat + if hasattr(ps, 'to_dict'): + ps_to_log = ps.to_dict() + else: + ps_to_log = ps + log.debug('saving project-specific settings:\n%s', pformat(ps_to_log)) + + all_settings[project_id] = ps + prefs['project_settings'] = all_settings