Compare commits

..

19 Commits

Author SHA1 Message Date
fb5433d473 Bumped version to 1.7.5 2017-10-06 12:39:38 +02:00
a17fe45712 Allow overriding the render output path on a per-scene basis. 2017-10-06 12:39:18 +02:00
1bfba64bdc Formatting 2017-10-06 12:38:17 +02:00
cdb4bf4f4f Renamed 'Job File Path' to 'Job Storage Path' so it's more explicit. 2017-10-06 12:37:44 +02:00
15254b8951 Sorting the project list in user prefs alphabetically 2017-10-06 12:35:25 +02:00
3ed5f2c187 Bumped version to 1.7.4 2017-09-05 11:26:52 +02:00
0be3bf7f49 Fixed unit test, it still mocked sys.platform
We now use platform.system() to detect the platform.
2017-09-05 11:25:25 +02:00
f207e14664 Added link to changelog 2017-09-05 11:16:22 +02:00
9932003400 Fix T48852: screenshot always shows "Communicating with Blender Cloud" 2017-09-05 11:16:17 +02:00
e7035e6f0c Updated changelog 2017-09-05 11:11:31 +02:00
014a36d24e Fix T52621: class name collision upon add-on registration
This is checked since Blender 2.79.
2017-09-05 11:07:33 +02:00
068451a7aa Mark 1.7.3 as released in changelog 2017-09-05 11:06:28 +02:00
56fb1ec3df Bumped version to 1.7.3 2017-08-08 12:46:07 +02:00
e93094cb88 Default to scene frame range when no frame range is given. 2017-07-03 11:09:31 +02:00
33718a1a35 Removed test print statement 2017-07-03 11:09:00 +02:00
db82dbe730 Updated changelog 2017-07-03 09:16:01 +02:00
8d405330ee Better platform detection.
The sys.platform string is 'win32' even on 64-bit Windows. Furthermore,
we expect 'windows', not 'win32'. platform.system().lower() gives us this.
2017-07-03 09:14:27 +02:00
66ddc7b47b Fixed issue running BAM on Windows.
I found this solution in a Django bug report:
 https://code.djangoproject.com/ticket/24160
2017-07-03 09:13:49 +02:00
2fa8cb4054 Refuse to render on Flamenco before blend file is saved at least once.
The file should have a location on the filesystem before BAM can pick it up.
2017-07-03 08:41:26 +02:00
10 changed files with 181 additions and 37 deletions

View File

@@ -1,5 +1,26 @@
# Blender Cloud changelog
## 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.
- Refuse to render on Flamenco before blend file is saved at least once.
- Fixed some Windows-specific issues.
## Version 1.7.2 (2017-06-22)

View File

@@ -21,7 +21,7 @@
bl_info = {
'name': 'Blender Cloud',
"author": "Sybren A. Stüvel, Francesco Siddi, Inês Almeida, Antony Riakiotakis",
'version': (1, 7, 2),
'version': (1, 7, 5),
'blender': (2, 77, 0),
'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 '

View File

@@ -173,7 +173,7 @@ class AttractPollMixin:
return attract_is_active
class ToolsPanel(AttractPollMixin, Panel):
class AttractToolsPanel(AttractPollMixin, Panel):
bl_label = 'Attract'
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'UI'
@@ -974,7 +974,7 @@ def register():
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(AttractShotDelete)
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.
# This assumption is true for the Blender Institute.
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',
subtype='DIR_PATH',
default='/render/_flamenco/storage')
@@ -571,7 +571,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all,
{'where': {'user': self.user_id,
'category': {'$ne': 'home'}},
'sort': '-_created',
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
@@ -581,7 +581,7 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
pillarsdk.Project.all,
{'where': {'user': {'$ne': self.user_id},
'permissions.groups.group': {'$in': self.db_user.groups}},
'sort': '-_created',
'sort': '-name',
'projection': {'_id': True,
'name': True,
'extension_props': True},
@@ -605,7 +605,10 @@ class PILLAR_OT_projects(async_loop.AsyncModalOperatorMixin,
projects = list(reduce_properties(projects_user['_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()

View File

@@ -20,12 +20,13 @@
The preferences are managed blender.py, the rest of the Flamenco-specific stuff is here.
"""
import functools
import logging
import os
from pathlib import Path, PurePath
import typing
if "bpy" in locals():
import importlib
@@ -37,7 +38,6 @@ if "bpy" in locals():
else:
from . import bam_interface, sdk
import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty, IntProperty
@@ -45,7 +45,6 @@ from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolPropert
from .. import async_loop, pillar
from ..utils import pyside_cache, redraw
log = logging.getLogger(__name__)
# Global flag used to determine whether panels etc. can be drawn.
@@ -150,6 +149,14 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
log = logging.getLogger('%s.FLAMENCO_OT_render' % __name__)
async def async_execute(self, context):
# Refuse to start if the file hasn't been saved. It's okay if
# it's dirty, but we do need a filename and a location.
if not os.path.exists(context.blend_data.filepath):
self.report({'ERROR'}, 'Please save your Blend file before using '
'the Blender Cloud addon.')
self.quit()
return
if not await self.authenticate(context):
return
@@ -192,10 +199,12 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
# Create the job at Flamenco Server.
context.window_manager.flamenco_status = 'COMMUNICATING'
frame_range = scene.flamenco_render_frame_range.strip() or scene_frame_range(context)
settings = {'blender_cmd': '{blender}',
'chunk_size': scene.flamenco_render_fchunk_size,
'filepath': manager.replace_path(outfile),
'frames': scene.flamenco_render_frame_range,
'frames': frame_range,
'render_output': manager.replace_path(render_output),
}
@@ -348,6 +357,13 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
return outfile, missing_sources
def scene_frame_range(context) -> str:
"""Returns the frame range string for the current scene."""
s = context.scene
return '%i-%i' % (s.frame_start, s.frame_end)
class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
"""Sets the scene frame range as the Flamenco render frame range."""
bl_idname = 'flamenco.scene_to_frame_range'
@@ -355,8 +371,7 @@ class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
bl_description = __doc__.rstrip('.')
def execute(self, context):
s = context.scene
s.flamenco_render_frame_range = '%i-%i' % (s.frame_start, s.frame_end)
context.scene.flamenco_render_frame_range = scene_frame_range(context)
return {'FINISHED'}
@@ -437,6 +452,30 @@ class FLAMENCO_OT_explore_file_path(FlamencoPollMixin,
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,
project_id: str,
manager_id: str,
@@ -491,6 +530,7 @@ def _render_output_path(
flamenco_job_output_path: str,
render_image_format: str,
flamenco_render_frame_range: str,
include_rel_path: bool = True,
) -> typing.Optional[PurePath]:
"""Cached version of render_output_path()
@@ -515,8 +555,7 @@ def _render_output_path(
except ValueError:
return None
rel_parts = proj_rel.parts[flamenco_job_output_strip_components:]
output_top = Path(flamenco_job_output_path)
output_top = PurePath(flamenco_job_output_path)
# 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'.
@@ -524,7 +563,11 @@ def _render_output_path(
if stem.endswith('.flamenco'):
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.
if is_image_type(render_image_format):
@@ -549,13 +592,19 @@ def render_output_path(context, filepath: Path = None) -> typing.Optional[PurePa
if filepath is None:
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(
prefs.cloud_project_local_path,
filepath,
prefs.flamenco_job_output_strip_components,
prefs.flamenco_job_output_path,
job_output_path,
scene.render.image_settings.file_format,
scene.flamenco_render_frame_range,
include_rel_path=not scene.flamenco_do_override_output_path,
)
@@ -589,8 +638,9 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
if getattr(context.scene, 'flamenco_render_job_type', None) == 'blender-render-progressive':
layout.prop(context.scene, 'flamenco_render_schunk_count')
readonly_stuff = layout.column(align=True)
labeled_row = readonly_stuff.split(0.25, align=True)
paths_layout = layout.column(align=True)
labeled_row = paths_layout.split(0.25, align=True)
labeled_row.label('Storage:')
prop_btn_row = labeled_row.row(align=True)
prop_btn_row.label(prefs.flamenco_job_file_path)
@@ -598,19 +648,33 @@ class FLAMENCO_PT_render(bpy.types.Panel, FlamencoPollMixin):
text='', icon='DISK_DRIVE')
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)
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:
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,
text='', icon='DISK_DRIVE')
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
if flamenco_status == 'IDLE':
layout.operator(FLAMENCO_OT_render.bl_idname,
@@ -642,6 +706,22 @@ def deactivate():
_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():
from ..utils import redraw
@@ -651,6 +731,8 @@ def register():
bpy.utils.register_class(FLAMENCO_OT_scene_to_frame_range)
bpy.utils.register_class(FLAMENCO_OT_copy_files)
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)
scene = bpy.types.Scene
@@ -688,6 +770,19 @@ def register():
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(
items=[
('IDLE', 'IDLE', 'Not doing anything.'),
@@ -724,6 +819,14 @@ def unregister():
del bpy.types.Scene.flamenco_render_job_priority
except AttributeError:
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:
del bpy.types.WindowManager.flamenco_status
except AttributeError:

View File

@@ -50,6 +50,7 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
"""
import asyncio
import os
import shlex
import subprocess
@@ -76,9 +77,17 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
else:
pythonpath = wheel_pythonpath_278()
env = {
'PYTHONPATH': pythonpath,
# Needed on Windows because http://bugs.python.org/issue8557
'PATH': os.environ['PATH'],
}
if 'SYSTEMROOT' in os.environ: # Windows http://bugs.python.org/issue20614
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
proc = await asyncio.create_subprocess_exec(
*args,
env={'PYTHONPATH': pythonpath},
env=env,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,

View File

@@ -11,21 +11,20 @@ class Manager(List, Find):
@functools.lru_cache()
def _sorted_path_replacements(self) -> list:
import sys
import platform
if self.path_replacement is None:
return []
print('SORTING PATH REPLACEMENTS')
items = self.path_replacement.to_dict().items()
def by_length(item):
return -len(item[0]), item[0]
platform = sys.platform
return [(varname, platform_replacements[platform])
for varname, platform_replacements in sorted(items, key=by_length)]
this_platform = platform.system().lower()
return [(varname, platform_replacements[this_platform])
for varname, platform_replacements in sorted(items, key=by_length)
if this_platform in platform_replacements]
def replace_path(self, some_path: pathlib.PurePath) -> str:
"""Performs path variable replacement.

View File

@@ -112,7 +112,11 @@ class PILLAR_OT_image_share(pillar.PillarOperatorMixin,
async def async_execute(self, context):
"""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:
# Refresh credentials

View File

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

View File

@@ -83,8 +83,13 @@ class PathReplacementTest(unittest.TestCase):
def _do_test(self, test_paths, platform, 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:
as_path_instance = pathclass(input_path)
self.assertEqual(expected_result,
self.test_manager.replace_path(pathclass(input_path)),
'for input %s on platform %s' % (input_path, platform))
self.test_manager.replace_path(as_path_instance),
'for input %r on platform %s' % (as_path_instance, platform))