Compare commits

..

12 Commits

8 changed files with 140 additions and 28 deletions

View File

@@ -1,6 +1,21 @@
# Blender Cloud changelog # Blender Cloud changelog
## Version 1.7.3 (in development) ## Version 1.7.5 (2017-10-06)
- Sorting the project list alphabetically.
- Renamed 'Job File Path' to 'Job Storage Path' so it's more explicit.
- Allow overriding the render output path on a per-scene basis.
## Version 1.7.4 (2017-09-05)
- Fix [T52621](https://developer.blender.org/T52621): Fixed class name collision upon add-on
registration. This is checked since Blender 2.79.
- Fix [T48852](https://developer.blender.org/T48852): Screenshot no longer shows "Communicating with
Blender Cloud".
## Version 1.7.3 (2017-08-08)
- Default to scene frame range when no frame range is given. - Default to scene frame range when no frame range is given.
- Refuse to render on Flamenco before blend file is saved at least once. - Refuse to render on Flamenco before blend file is saved at least once.

View File

@@ -21,7 +21,7 @@
bl_info = { bl_info = {
'name': 'Blender Cloud', 'name': 'Blender Cloud',
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis", "author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
'version': (1, 7, 3), 'version': (1, 7, 5),
'blender': (2, 77, 0), 'blender': (2, 77, 0),
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser', 'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon ' 'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '

View File

@@ -173,7 +173,7 @@ class AttractPollMixin:
return attract_is_active return attract_is_active
class ToolsPanel(AttractPollMixin, Panel): class AttractToolsPanel(AttractPollMixin, Panel):
bl_label = 'Attract' bl_label = 'Attract'
bl_space_type = 'SEQUENCE_EDITOR' bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'UI' bl_region_type = 'UI'
@@ -974,7 +974,7 @@ def register():
bpy.types.SEQUENCER_PT_edit.append(draw_strip_movie_meta) bpy.types.SEQUENCER_PT_edit.append(draw_strip_movie_meta)
bpy.utils.register_class(ToolsPanel) bpy.utils.register_class(AttractToolsPanel)
bpy.utils.register_class(AttractShotRelink) bpy.utils.register_class(AttractShotRelink)
bpy.utils.register_class(AttractShotDelete) bpy.utils.register_class(AttractShotDelete)
bpy.utils.register_class(AttractStripUnlink) bpy.utils.register_class(AttractStripUnlink)

View File

@@ -236,7 +236,7 @@ class BlenderCloudPreferences(AddonPreferences):
# NOTE: The assumption is that the workers can also find the files in the same path. # NOTE: The assumption is that the workers can also find the files in the same path.
# This assumption is true for the Blender Institute. # This assumption is true for the Blender Institute.
flamenco_job_file_path = StringProperty( flamenco_job_file_path = StringProperty(
name='Job File Path', name='Job Storage Path',
description='Path where to store job files, should be accesible for Workers too', description='Path where to store job files, should be accesible for Workers too',
subtype='DIR_PATH', subtype='DIR_PATH',
default='/render/_flamenco/storage') default='/render/_flamenco/storage')
@@ -571,7 +571,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all, pillarsdk.Project.all,
{'where': {'user': self.user_id, {'where': {'user': self.user_id,
'category': {'$ne': 'home'}}, 'category': {'$ne': 'home'}},
'sort': '-_created', 'sort': '-name',
'projection': {'_id': True, 'projection': {'_id': True,
'name': True, 'name': True,
'extension_props': True}, 'extension_props': True},
@@ -581,7 +581,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all, pillarsdk.Project.all,
{'where': {'user': {'$ne': self.user_id}, {'where': {'user': {'$ne': self.user_id},
'permissions.groups.group': {'$in': self.db_user.groups}}, 'permissions.groups.group': {'$in': self.db_user.groups}},
'sort': '-_created', 'sort': '-name',
'projection': {'_id': True, 'projection': {'_id': True,
'name': True, 'name': True,
'extension_props': True}, 'extension_props': True},
@@ -605,7 +605,10 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
projects = list(reduce_properties(projects_user['_items'])) + \ projects = list(reduce_properties(projects_user['_items'])) + \
list(reduce_properties(projects_shared['_items'])) list(reduce_properties(projects_shared['_items']))
preferences().project.available_projects = projects def proj_sort_key(project):
return project.get('name')
preferences().project.available_projects = sorted(projects, key=proj_sort_key)
self.quit() self.quit()

View File

@@ -27,7 +27,6 @@ import os
from pathlib import Path, PurePath from pathlib import Path, PurePath
import typing import typing
if "bpy" in locals(): if "bpy" in locals():
import importlib import importlib
@@ -39,7 +38,6 @@ if "bpy" in locals():
else: else:
from . import bam_interface, sdk from . import bam_interface, sdk
import bpy import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty
@@ -47,7 +45,6 @@ from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolPropert
from .. import async_loop, pillar from .. import async_loop, pillar
from ..utils import pyside_cache, redraw from ..utils import pyside_cache, redraw
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
# Global flag used to determine whether panels etc. can be drawn. # Global flag used to determine whether panels etc. can be drawn.
@@ -455,6 +452,30 @@ class FLAMENCO_OT_explore_file_path(FlamencoPollMixin,
return {'FINISHED'} return {'FINISHED'}
class FLAMENCO_OT_enable_output_path_override(Operator):
"""Enables the 'override output path' setting."""
bl_idname = 'flamenco.enable_output_path_override'
bl_label = 'Enable Overriding of Output Path'
bl_description = 'Click to specify a non-default Output Path for this particular job'
def execute(self, context):
context.scene.flamenco_do_override_output_path = True
return {'FINISHED'}
class FLAMENCO_OT_disable_output_path_override(Operator):
"""Disables the 'override output path' setting."""
bl_idname = 'flamenco.disable_output_path_override'
bl_label = 'disable Overriding of Output Path'
bl_description = 'Click to use the default Output Path'
def execute(self, context):
context.scene.flamenco_do_override_output_path = False
return {'FINISHED'}
async def create_job(user_id: str, async def create_job(user_id: str,
project_id: str, project_id: str,
manager_id: str, manager_id: str,
@@ -509,6 +530,7 @@ def _render_output_path(
flamenco_job_output_path: str, flamenco_job_output_path: str,
render_image_format: str, render_image_format: str,
flamenco_render_frame_range: str, flamenco_render_frame_range: str,
include_rel_path: bool = True,
) -> typing.Optional[PurePath]: ) -> typing.Optional[PurePath]:
"""Cached version of render_output_path() """Cached version of render_output_path()
@@ -533,8 +555,7 @@ def _render_output_path(
except ValueError: except ValueError:
return None return None
rel_parts = proj_rel.parts[flamenco_job_output_strip_components:] output_top = PurePath(flamenco_job_output_path)
output_top = Path(flamenco_job_output_path)
# Strip off '.flamenco' too; we use 'xxx.flamenco.blend' as job file, but # Strip off '.flamenco' too; we use 'xxx.flamenco.blend' as job file, but
# don't want to have all the output paths ending in '.flamenco'. # don't want to have all the output paths ending in '.flamenco'.
@@ -542,7 +563,11 @@ def _render_output_path(
if stem.endswith('.flamenco'): if stem.endswith('.flamenco'):
stem = stem[:-9] stem = stem[:-9]
dir_components = output_top.joinpath(*rel_parts) / stem if include_rel_path:
rel_parts = proj_rel.parts[flamenco_job_output_strip_components:]
dir_components = output_top.joinpath(*rel_parts) / stem
else:
dir_components = output_top
# Blender will have to append the file extensions by itself. # Blender will have to append the file extensions by itself.
if is_image_type(render_image_format): if is_image_type(render_image_format):
@@ -567,13 +592,19 @@ def render_output_path(context, filepath: Path = None) -> typing.Optional[PurePa
if filepath is None: if filepath is None:
filepath = Path(context.blend_data.filepath) filepath = Path(context.blend_data.filepath)
if scene.flamenco_do_override_output_path:
job_output_path = scene.flamenco_override_output_path
else:
job_output_path = prefs.flamenco_job_output_path
return _render_output_path( return _render_output_path(
prefs.cloud_project_local_path, prefs.cloud_project_local_path,
filepath, filepath,
prefs.flamenco_job_output_strip_components, prefs.flamenco_job_output_strip_components,
prefs.flamenco_job_output_path, job_output_path,
scene.render.image_settings.file_format, scene.render.image_settings.file_format,
scene.flamenco_render_frame_range, scene.flamenco_render_frame_range,
include_rel_path=not scene.flamenco_do_override_output_path,
) )
@@ -607,8 +638,9 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
if getattr(context.scene, 'flamenco_render_job_type', None) == 'blender-render-progressive': if getattr(context.scene, 'flamenco_render_job_type', None) == 'blender-render-progressive':
layout.prop(context.scene, 'flamenco_render_schunk_count') layout.prop(context.scene, 'flamenco_render_schunk_count')
readonly_stuff = layout.column(align=True) paths_layout = layout.column(align=True)
labeled_row = readonly_stuff.split(0.25, align=True)
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Storage:') labeled_row.label('Storage:')
prop_btn_row = labeled_row.row(align=True) prop_btn_row = labeled_row.row(align=True)
prop_btn_row.label(prefs.flamenco_job_file_path) prop_btn_row.label(prefs.flamenco_job_file_path)
@@ -616,19 +648,33 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
text='', icon='DISK_DRIVE') text='', icon='DISK_DRIVE')
props.path = prefs.flamenco_job_file_path props.path = prefs.flamenco_job_file_path
labeled_row = readonly_stuff.split(0.25, align=True)
labeled_row.label('Output:')
prop_btn_row = labeled_row.row(align=True)
render_output = render_output_path(context) render_output = render_output_path(context)
if render_output is None: if render_output is None:
prop_btn_row.label('Unable to render with Flamenco, outside of project directory.') paths_layout.label('Unable to render with Flamenco, outside of project directory.')
else: else:
prop_btn_row.label(str(render_output)) labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Output:')
prop_btn_row = labeled_row.row(align=True)
if context.scene.flamenco_do_override_output_path:
prop_btn_row.prop(context.scene, 'flamenco_override_output_path', text='')
op = FLAMENCO_OT_disable_output_path_override.bl_idname
icon = 'X'
else:
prop_btn_row.label(str(render_output))
op = FLAMENCO_OT_enable_output_path_override.bl_idname
icon = 'GREASEPENCIL'
prop_btn_row.operator(op, icon=icon, text='')
props = prop_btn_row.operator(FLAMENCO_OT_explore_file_path.bl_idname, props = prop_btn_row.operator(FLAMENCO_OT_explore_file_path.bl_idname,
text='', icon='DISK_DRIVE') text='', icon='DISK_DRIVE')
props.path = str(render_output.parent) props.path = str(render_output.parent)
if context.scene.flamenco_do_override_output_path:
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Effective Output Path:')
labeled_row.label(str(render_output))
flamenco_status = context.window_manager.flamenco_status flamenco_status = context.window_manager.flamenco_status
if flamenco_status == 'IDLE': if flamenco_status == 'IDLE':
layout.operator(FLAMENCO_OT_render.bl_idname, layout.operator(FLAMENCO_OT_render.bl_idname,
@@ -660,6 +706,22 @@ def deactivate():
_render_output_path.cache_clear() _render_output_path.cache_clear()
def flamenco_do_override_output_path_updated(scene, context):
"""Set the override paths to the default, if not yet set."""
# Only set a default when enabling the override.
if not scene.flamenco_do_override_output_path:
return
# Don't overwrite existing setting.
if scene.flamenco_override_output_path:
return
from ..blender import preferences
scene.flamenco_override_output_path = preferences().flamenco_job_output_path
log.info('Setting Override Output Path to %s', scene.flamenco_override_output_path)
def register(): def register():
from ..utils import redraw from ..utils import redraw
@@ -669,6 +731,8 @@ def register():
bpy.utils.register_class(FLAMENCO_OT_scene_to_frame_range) bpy.utils.register_class(FLAMENCO_OT_scene_to_frame_range)
bpy.utils.register_class(FLAMENCO_OT_copy_files) bpy.utils.register_class(FLAMENCO_OT_copy_files)
bpy.utils.register_class(FLAMENCO_OT_explore_file_path) bpy.utils.register_class(FLAMENCO_OT_explore_file_path)
bpy.utils.register_class(FLAMENCO_OT_enable_output_path_override)
bpy.utils.register_class(FLAMENCO_OT_disable_output_path_override)
bpy.utils.register_class(FLAMENCO_PT_render) bpy.utils.register_class(FLAMENCO_PT_render)
scene = bpy.types.Scene scene = bpy.types.Scene
@@ -706,6 +770,19 @@ def register():
description='Higher numbers mean higher priority' description='Higher numbers mean higher priority'
) )
scene.flamenco_do_override_output_path = BoolProperty(
name='Override Output Path for this Job',
description='When enabled, allows you to specify a non-default Output path '
'for this particular job',
default=False,
update=flamenco_do_override_output_path_updated
)
scene.flamenco_override_output_path = StringProperty(
name='Override Output Path',
description='Path where to store output files, should be accessible for Workers',
subtype='DIR_PATH',
default='')
bpy.types.WindowManager.flamenco_status = EnumProperty( bpy.types.WindowManager.flamenco_status = EnumProperty(
items=[ items=[
('IDLE', 'IDLE', 'Not doing anything.'), ('IDLE', 'IDLE', 'Not doing anything.'),
@@ -742,6 +819,14 @@ def unregister():
del bpy.types.Scene.flamenco_render_job_priority del bpy.types.Scene.flamenco_render_job_priority
except AttributeError: except AttributeError:
pass pass
try:
del bpy.types.Scene.flamenco_do_override_output_path
except AttributeError:
pass
try:
del bpy.types.Scene.flamenco_override_output_path
except AttributeError:
pass
try: try:
del bpy.types.WindowManager.flamenco_status del bpy.types.WindowManager.flamenco_status
except AttributeError: except AttributeError:

View File

@@ -112,7 +112,11 @@ class PILLAR_OT_image_share(pillar.PillarOperatorMixin,
async def async_execute(self, context): async def async_execute(self, context):
"""Entry point of the asynchronous operator.""" """Entry point of the asynchronous operator."""
self.report({'INFO'}, 'Communicating with Blender Cloud') # We don't want to influence what is included in the screen shot.
if self.target == 'SCREENSHOT':
print('Blender Cloud add-on is communicating with Blender Cloud')
else:
self.report({'INFO'}, 'Communicating with Blender Cloud')
try: try:
# Refresh credentials # Refresh credentials

View File

@@ -232,7 +232,7 @@ setup(
'wheels': BuildWheels}, 'wheels': BuildWheels},
name='blender_cloud', name='blender_cloud',
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.', description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
version='1.7.3', version='1.7.5',
author='Sybren A. Stüvel', author='Sybren A. Stüvel',
author_email='sybren@stuvel.eu', author_email='sybren@stuvel.eu',
packages=find_packages('.'), packages=find_packages('.'),

View File

@@ -83,8 +83,13 @@ class PathReplacementTest(unittest.TestCase):
def _do_test(self, test_paths, platform, pathclass): def _do_test(self, test_paths, platform, pathclass):
self.test_manager.PurePlatformPath = pathclass self.test_manager.PurePlatformPath = pathclass
with unittest.mock.patch('sys.platform', platform):
def mocked_system():
return platform
with unittest.mock.patch('platform.system', mocked_system):
for expected_result, input_path in test_paths: for expected_result, input_path in test_paths:
as_path_instance = pathclass(input_path)
self.assertEqual(expected_result, self.assertEqual(expected_result,
self.test_manager.replace_path(pathclass(input_path)), self.test_manager.replace_path(as_path_instance),
'for input %s on platform %s' % (input_path, platform)) 'for input %r on platform %s' % (as_path_instance, platform))