Compare commits

..

7 Commits

Author SHA1 Message Date
ec5f317dac Bumped version to 1.7.0 2017-06-09 11:04:49 +02:00
a51f61d9b5 Added translation: path → path replacement variable 2017-06-09 10:52:56 +02:00
13bc9a89c8 Updated changelog 2017-05-03 15:33:52 +02:00
996b722813 Fixed bug where a symlinked project path caused an issue
Blender would report that the blend file wasn't in the project path, even
though it was. This was caused by resolving symlinks in the project path,
but not in the blendfile path.
2017-05-03 15:33:22 +02:00
e7f2567bfc Clear a LRU cache when (de)activating Flamenco 2017-05-03 15:32:32 +02:00
ff8e71c542 Fixed reloading after upgrading from 1.4.4. 2017-05-03 12:12:58 +02:00
543da5c8d8 Flamenco exclusion filter requires BAM 1.1.7; this is now checked
A warning is shown in the GUI when a BAM version that's too old is used
(instead of simply crashing when an exclusion filter was specified).
2017-05-02 18:48:54 +02:00
8 changed files with 222 additions and 17 deletions

View File

@@ -1,6 +1,13 @@
# Blender Cloud changelog
## Version 1.7.0 (2017-06-09)
- Fixed reloading after upgrading from 1.4.4 (our last public release).
- Fixed bug handling a symlinked project path.
- Added support for Manager-defined path replacement variables.
## Version 1.6.4 (2017-04-21)
- Added file exclusion filter for Flamenco. A filter like "*.abc;*.mkv;*.mov" can be

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, 6, 4),
'version': (1, 7, 0),
'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 '
@@ -65,21 +65,28 @@ def register():
def reload_mod(name):
modname = '%s.%s' % (__name__, name)
module = importlib.reload(sys.modules[modname])
sys.modules[modname] = module
return module
try:
old_module = sys.modules[modname]
except KeyError:
# Wasn't loaded before -- can happen after an upgrade.
new_module = importlib.import_module(modname)
else:
new_module = importlib.reload(old_module)
sys.modules[modname] = new_module
return new_module
reload_mod('blendfile')
reload_mod('home_project')
reload_mod('utils')
blender = reload_mod('blender')
async_loop = reload_mod('async_loop')
flamenco = reload_mod('flamenco')
attract = reload_mod('attract')
texture_browser = reload_mod('texture_browser')
settings_sync = reload_mod('settings_sync')
image_sharing = reload_mod('image_sharing')
attract = reload_mod('attract')
flamenco = reload_mod('flamenco')
blender = reload_mod('blender')
else:
from . import (blender, texture_browser, async_loop, settings_sync, blendfile, home_project,
image_sharing, attract, flamenco)
@@ -88,11 +95,11 @@ def register():
async_loop.register()
flamenco.register()
attract.register()
texture_browser.register()
blender.register()
settings_sync.register()
image_sharing.register()
attract.register()
blender.register()
blender.handle_project_update()

View File

@@ -426,6 +426,8 @@ class BlenderCloudPreferences(AddonPreferences):
text='Local Cloud Project Path')
def draw_flamenco_buttons(self, flamenco_box, bcp: flamenco.FlamencoManagerGroup, context):
from .flamenco import bam_interface
header_row = flamenco_box.row(align=True)
header_row.label('Flamenco:', icon_value=icon('CLOUD'))
@@ -461,7 +463,15 @@ class BlenderCloudPreferences(AddonPreferences):
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
props.path = self.flamenco_job_output_path
job_output_box.prop(self, 'flamenco_exclude_filter')
show_warning = bool(self.flamenco_exclude_filter and
not bam_interface.bam_supports_exclude_option())
job_output_box.alert = show_warning
job_output_box.prop(self, 'flamenco_exclude_filter',
icon='ERROR' if show_warning else 'NONE')
if show_warning:
job_output_box.label(
text='Warning, the exclusion filter requires a newer version of Blender!')
job_output_box.alert = False
prop_split = job_output_box.split(0.32, align=True)
prop_split.label('Strip Components:')

View File

@@ -139,6 +139,9 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
if not await self.authenticate(context):
return
import pillarsdk.exceptions
from .sdk import Manager
from ..pillar import pillar_call
from ..blender import preferences
scene = context.scene
@@ -160,15 +163,26 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
if not outfile:
return
# Create the job at Flamenco Server.
# Fetch Manager for doing path replacement.
self.log.info('Going to fetch manager %s', self.user_id)
prefs = preferences()
manager_id = prefs.flamenco_manager.manager
try:
manager = await pillar_call(Manager.find, manager_id)
except pillarsdk.exceptions.ResourceNotFound:
self.report({'ERROR'}, 'Manager %s not found, refresh your managers in '
'the Blender Cloud add-on settings.' % manager_id)
self.quit()
return
# Create the job at Flamenco Server.
context.window_manager.flamenco_status = 'COMMUNICATING'
settings = {'blender_cmd': '{blender}',
'chunk_size': scene.flamenco_render_fchunk_size,
'filepath': str(outfile),
'filepath': manager.replace_path(outfile),
'frames': scene.flamenco_render_frame_range,
'render_output': str(render_output),
'render_output': manager.replace_path(render_output),
}
# Add extra settings specific to the job type
@@ -188,7 +202,7 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
try:
job_info = await create_job(self.user_id,
prefs.project.project,
prefs.flamenco_manager.manager,
manager_id,
scene.flamenco_render_job_type,
settings,
'Render %s' % filepath.name,
@@ -477,7 +491,13 @@ def _render_output_path(
return None
try:
proj_rel = blend_filepath.parent.relative_to(project_path)
blend_abspath = blend_filepath.resolve().absolute()
except FileNotFoundError:
# Path.resolve() will raise a FileNotFoundError if the path doesn't exist.
return None
try:
proj_rel = blend_abspath.parent.relative_to(project_path)
except ValueError:
return None
@@ -596,6 +616,7 @@ def activate():
global flamenco_is_active
log.info('Activating Flamenco')
flamenco_is_active = True
_render_output_path.cache_clear()
def deactivate():
@@ -604,6 +625,7 @@ def deactivate():
global flamenco_is_active
log.info('Deactivating Flamenco')
flamenco_is_active = False
_render_output_path.cache_clear()
def register():

View File

@@ -1,5 +1,6 @@
"""BAM packing interface for Flamenco."""
import functools
import logging
from pathlib import Path
import typing
@@ -14,6 +15,28 @@ class CommandExecutionError(Exception):
pass
if 'bam_supports_exclude_option' in locals():
locals()['bam_supports_exclude_option'].cache_clear()
@functools.lru_cache(maxsize=1)
def bam_supports_exclude_option() -> bool:
"""Returns True if the version of BAM bundled with Blender supports --exclude.
This feature was added to BAM 1.1.7, so we can do a simple version check.
"""
try:
import io_blend_utils
except ImportError:
# If this happens, BAM won't work at all. However, this function can be called from
# the GUI; by being a bit careful while importing, we avoid breaking Blender's GUI.
log.exception('Error importing io_blend_utils module.')
return False
return io_blend_utils.bl_info['version'] >= (1, 1, 7)
async def bam_copy(base_blendfile: Path, target_blendfile: Path,
exclusion_filter: str) -> typing.List[Path]:
"""Uses BAM to copy the given file and dependencies to the target blendfile.
@@ -43,7 +66,11 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
]
if exclusion_filter:
args.extend(['--exclude', exclusion_filter])
if bam_supports_exclude_option():
args.extend(['--exclude', exclusion_filter])
else:
log.warning('Your version of Blender does not support the exclusion filter, '
'copying all files.')
cmd_to_log = ' '.join(shlex.quote(s) for s in args)
log.info('Executing %s', cmd_to_log)

View File

@@ -1,9 +1,51 @@
import functools
import pathlib
from pillarsdk.resource import List, Find, Create
class Manager(List, Find):
"""Manager class wrapping the REST nodes endpoint"""
path = 'flamenco/managers'
PurePlatformPath = pathlib.PurePath
@functools.lru_cache()
def _sorted_path_replacements(self) -> list:
import sys
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)]
def replace_path(self, some_path: pathlib.PurePath) -> str:
"""Performs path variable replacement.
Tries to find platform-specific path prefixes, and replaces them with
variables.
"""
for varname, path in self._sorted_path_replacements():
replacement = self.PurePlatformPath(path)
try:
relpath = some_path.relative_to(replacement)
except ValueError:
# Not relative to each other, so no replacement possible
continue
replacement_root = self.PurePlatformPath('{%s}' % varname)
return (replacement_root / relpath).as_posix()
return some_path.as_posix()
class Job(List, Find, Create):

View File

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

View File

@@ -0,0 +1,90 @@
"""Unittests for blender_cloud.utils.
This unittest requires bpy to be importable, so build Blender as a module and install it
into your virtualenv. See https://stuvel.eu/files/bconf2016/#/10 for notes how.
"""
import datetime
import pathlib
import unittest.mock
import pillarsdk.utils
from blender_cloud.flamenco import sdk
class PathReplacementTest(unittest.TestCase):
def setUp(self):
self.test_manager = sdk.Manager({
'_created': datetime.datetime(2017, 5, 31, 15, 12, 32, tzinfo=pillarsdk.utils.utc),
'_etag': 'c39942ee4bcc4658adcc21e4bcdfb0ae',
'_id': '592edd609837732a2a272c62',
'_updated': datetime.datetime(2017, 6, 8, 14, 51, 3, tzinfo=pillarsdk.utils.utc),
'description': 'Manager formerly known as "testman"',
'job_types': {'sleep': {'vars': {}}},
'name': '<script>alert("this is a manager")</script>',
'owner': '592edd609837732a2a272c63',
'path_replacement': {'job_storage': {'darwin': '/Volume/shared',
'linux': '/shared',
'windows': 's:/'},
'render': {'darwin': '/Volume/render/',
'linux': '/render/',
'windows': 'r:/'},
'longrender': {'darwin': '/Volume/render/long',
'linux': '/render/long',
'windows': 'r:/long'},
},
'projects': ['58cbdd5698377322d95eb55e'],
'service_account': '592edd609837732a2a272c60',
'stats': {'nr_of_workers': 3},
'url': 'http://192.168.3.101:8083/',
'user_groups': ['58cbdd5698377322d95eb55f'],
'variables': {'blender': {'darwin': '/opt/myblenderbuild/blender',
'linux': '/home/sybren/workspace/build_linux/bin/blender '
'--enable-new-depsgraph --factory-startup',
'windows': 'c:/temp/blender.exe'}}}
)
def test_linux(self):
# (expected result, input)
test_paths = [
('/doesnotexistreally', '/doesnotexistreally'),
('{render}/agent327/scenes/A_01_03_B', '/render/agent327/scenes/A_01_03_B'),
('{job_storage}/render/agent327/scenes', '/shared/render/agent327/scenes'),
('{longrender}/agent327/scenes', '/render/long/agent327/scenes'),
]
self._do_test(test_paths, 'linux', pathlib.PurePosixPath)
def test_windows(self):
# (expected result, input)
test_paths = [
('c:/doesnotexistreally', 'c:/doesnotexistreally'),
('c:/some/path', r'c:\some\path'),
('{render}/agent327/scenes/A_01_03_B', r'R:\agent327\scenes\A_01_03_B'),
('{render}/agent327/scenes/A_01_03_B', r'r:\agent327\scenes\A_01_03_B'),
('{render}/agent327/scenes/A_01_03_B', r'r:/agent327/scenes/A_01_03_B'),
('{job_storage}/render/agent327/scenes', 's:/render/agent327/scenes'),
('{longrender}/agent327/scenes', 'r:/long/agent327/scenes'),
]
self._do_test(test_paths, 'windows', pathlib.PureWindowsPath)
def test_darwin(self):
# (expected result, input)
test_paths = [
('/Volume/doesnotexistreally', '/Volume/doesnotexistreally'),
('{render}/agent327/scenes/A_01_03_B', r'/Volume/render/agent327/scenes/A_01_03_B'),
('{job_storage}/render/agent327/scenes', '/Volume/shared/render/agent327/scenes'),
('{longrender}/agent327/scenes', '/Volume/render/long/agent327/scenes'),
]
self._do_test(test_paths, 'darwin', pathlib.PurePosixPath)
def _do_test(self, test_paths, platform, pathclass):
self.test_manager.PurePlatformPath = pathclass
with unittest.mock.patch('sys.platform', platform):
for expected_result, input_path in test_paths:
self.assertEqual(expected_result,
self.test_manager.replace_path(pathclass(input_path)),
'for input %s on platform %s' % (input_path, platform))