Added translation: path → path replacement variable

This commit is contained in:
Sybren A. Stüvel 2017-06-09 10:52:56 +02:00
parent 13bc9a89c8
commit a51f61d9b5
4 changed files with 152 additions and 5 deletions

View File

@ -1,10 +1,11 @@
# Blender Cloud changelog # Blender Cloud changelog
## Version 1.6.4 (in development) ## Version 1.6.5 (in development)
- Fixed reloading after upgrading from 1.4.4 (our last public release). - Fixed reloading after upgrading from 1.4.4 (our last public release).
- Fixed bug handling a symlinked project path. - Fixed bug handling a symlinked project path.
- Added support for Manager-defined path replacement variables.
## Version 1.6.4 (2017-04-21) ## Version 1.6.4 (2017-04-21)

View File

@ -139,6 +139,9 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
if not await self.authenticate(context): if not await self.authenticate(context):
return return
import pillarsdk.exceptions
from .sdk import Manager
from ..pillar import pillar_call
from ..blender import preferences from ..blender import preferences
scene = context.scene scene = context.scene
@ -160,15 +163,26 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
if not outfile: if not outfile:
return 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() 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' context.window_manager.flamenco_status = 'COMMUNICATING'
settings = {'blender_cmd': '{blender}', settings = {'blender_cmd': '{blender}',
'chunk_size': scene.flamenco_render_fchunk_size, 'chunk_size': scene.flamenco_render_fchunk_size,
'filepath': str(outfile), 'filepath': manager.replace_path(outfile),
'frames': scene.flamenco_render_frame_range, '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 # Add extra settings specific to the job type
@ -188,7 +202,7 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
try: try:
job_info = await create_job(self.user_id, job_info = await create_job(self.user_id,
prefs.project.project, prefs.project.project,
prefs.flamenco_manager.manager, manager_id,
scene.flamenco_render_job_type, scene.flamenco_render_job_type,
settings, settings,
'Render %s' % filepath.name, 'Render %s' % filepath.name,

View File

@ -1,9 +1,51 @@
import functools
import pathlib
from pillarsdk.resource import List, Find, Create from pillarsdk.resource import List, Find, Create
class Manager(List, Find): class Manager(List, Find):
"""Manager class wrapping the REST nodes endpoint""" """Manager class wrapping the REST nodes endpoint"""
path = 'flamenco/managers' 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): class Job(List, Find, Create):

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))