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