Compare commits

...

19 Commits

Author SHA1 Message Date
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
e7b5c75046 Bumped version to 1.7.2 2017-06-22 15:09:39 +02:00
1d93bd9e5e Allow reloading of the Flamenco module with F8 2017-06-22 15:08:30 +02:00
ac2d0c033c Added missing parameter to function call 2017-06-22 15:08:30 +02:00
61fa63eb1d Compatibility fixes for Blender 2.78c
Blender 2.78c is shipped with a version of the io_blend_utils module that
doesn't have a `pythonpath()` function yet, and that's bundled with an
older version of BAM. To work around this, we ship BAM as wheel, and detect
whether this version is needed to run.

As an added bonus, Blender 2.78c can now also use the file exclude filter
for Flamenco. The `bam_supports_exclude_option()` function is thus no
longer necessary.
2017-06-22 15:08:30 +02:00
7022412889 Allow Pillar server URL overriding from environment 2017-06-14 12:56:34 +02:00
12 changed files with 126 additions and 57 deletions

View File

@@ -1,5 +1,24 @@
# Blender Cloud changelog # Blender Cloud changelog
## 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)
- Fixed compatibility with Blender 2.78c.
## Version 1.7.1 (2017-06-13) ## Version 1.7.1 (2017-06-13)

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, 1), 'version': (1, 7, 4),
'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

@@ -32,8 +32,7 @@ import rna_prop_ui
from . import pillar, async_loop, flamenco from . import pillar, async_loop, flamenco
from .utils import pyside_cache, redraw from .utils import pyside_cache, redraw
PILLAR_WEB_SERVER_URL = 'https://cloud.blender.org/' PILLAR_WEB_SERVER_URL = os.environ.get('BCLOUD_SERVER', 'https://cloud.blender.org/')
# PILLAR_WEB_SERVER_URL = 'http://pillar-web:5001/'
PILLAR_SERVER_URL = '%sapi/' % PILLAR_WEB_SERVER_URL PILLAR_SERVER_URL = '%sapi/' % PILLAR_WEB_SERVER_URL
ADDON_NAME = 'blender_cloud' ADDON_NAME = 'blender_cloud'
@@ -462,16 +461,7 @@ class BlenderCloudPreferences(AddonPreferences):
path_box.prop(self, 'flamenco_job_output_path', text='') path_box.prop(self, 'flamenco_job_output_path', text='')
props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE') props = path_box.operator('flamenco.explore_file_path', text='', icon='DISK_DRIVE')
props.path = self.flamenco_job_output_path 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 = job_output_box.split(0.32, align=True)
prop_split.label('Strip Components:') prop_split.label('Strip Components:')

View File

@@ -20,11 +20,26 @@
The preferences are managed blender.py, the rest of the Flamenco-specific stuff is here. The preferences are managed blender.py, the rest of the Flamenco-specific stuff is here.
""" """
import functools import functools
import logging import logging
import os
from pathlib import Path, PurePath from pathlib import Path, PurePath
import typing import typing
if "bpy" in locals():
import importlib
try:
bam_interface = importlib.reload(bam_interface)
sdk = importlib.reload(sdk)
except NameError:
from . import bam_interface, sdk
else:
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
@@ -32,6 +47,7 @@ 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.
@@ -136,6 +152,14 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
log = logging.getLogger('%s.FLAMENCO_OT_render' % __name__) log = logging.getLogger('%s.FLAMENCO_OT_render' % __name__)
async def async_execute(self, context): 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): if not await self.authenticate(context):
return return
@@ -178,10 +202,12 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
# Create the job at Flamenco Server. # Create the job at Flamenco Server.
context.window_manager.flamenco_status = 'COMMUNICATING' context.window_manager.flamenco_status = 'COMMUNICATING'
frame_range = scene.flamenco_render_frame_range.strip() or scene_frame_range(context)
settings = {'blender_cmd': '{blender}', settings = {'blender_cmd': '{blender}',
'chunk_size': scene.flamenco_render_fchunk_size, 'chunk_size': scene.flamenco_render_fchunk_size,
'filepath': manager.replace_path(outfile), 'filepath': manager.replace_path(outfile),
'frames': scene.flamenco_render_frame_range, 'frames': frame_range,
'render_output': manager.replace_path(render_output), 'render_output': manager.replace_path(render_output),
} }
@@ -301,7 +327,6 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
from datetime import datetime from datetime import datetime
from ..blender import preferences from ..blender import preferences
from . import bam_interface
prefs = preferences() prefs = preferences()
@@ -335,6 +360,13 @@ class FLAMENCO_OT_render(async_loop.AsyncModalOperatorMixin,
return outfile, missing_sources 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): class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
"""Sets the scene frame range as the Flamenco render frame range.""" """Sets the scene frame range as the Flamenco render frame range."""
bl_idname = 'flamenco.scene_to_frame_range' bl_idname = 'flamenco.scene_to_frame_range'
@@ -342,8 +374,7 @@ class FLAMENCO_OT_scene_to_frame_range(FlamencoPollMixin, Operator):
bl_description = __doc__.rstrip('.') bl_description = __doc__.rstrip('.')
def execute(self, context): def execute(self, context):
s = context.scene context.scene.flamenco_render_frame_range = scene_frame_range(context)
s.flamenco_render_frame_range = '%i-%i' % (s.frame_start, s.frame_end)
return {'FINISHED'} return {'FINISHED'}
@@ -359,14 +390,15 @@ class FLAMENCO_OT_copy_files(Operator,
async def async_execute(self, context): async def async_execute(self, context):
from pathlib import Path from pathlib import Path
from . import bam_interface
from ..blender import preferences from ..blender import preferences
context.window_manager.flamenco_status = 'PACKING' context.window_manager.flamenco_status = 'PACKING'
exclusion_filter = preferences().flamenco_exclude_filter or None
missing_sources = await bam_interface.bam_copy( missing_sources = await bam_interface.bam_copy(
Path(context.blend_data.filepath), Path(context.blend_data.filepath),
Path(preferences().flamenco_job_file_path), Path(preferences().flamenco_job_file_path),
exclusion_filter
) )
if missing_sources: if missing_sources:

View File

@@ -1,6 +1,5 @@
"""BAM packing interface for Flamenco.""" """BAM packing interface for Flamenco."""
import functools
import logging import logging
from pathlib import Path from pathlib import Path
import typing import typing
@@ -15,26 +14,26 @@ class CommandExecutionError(Exception):
pass pass
if 'bam_supports_exclude_option' in locals(): def wheel_pythonpath_278() -> str:
locals()['bam_supports_exclude_option'].cache_clear() """Returns the value of a PYTHONPATH environment variable needed to run BAM from its wheel file.
Workaround for Blender 2.78c not having io_blend_utils.pythonpath()
@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 os
import io_blend_utils from ..wheels import wheel_filename
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) # Find the wheel to run.
wheelpath = wheel_filename('blender_bam')
log.info('Using wheel %s to run BAM-Pack', wheelpath)
# Update the PYTHONPATH to include that wheel.
existing_pypath = os.environ.get('PYTHONPATH', '')
if existing_pypath:
return os.pathsep.join((existing_pypath, wheelpath))
return wheelpath
async def bam_copy(base_blendfile: Path, target_blendfile: Path, async def bam_copy(base_blendfile: Path, target_blendfile: Path,
@@ -51,6 +50,7 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
""" """
import asyncio import asyncio
import os
import shlex import shlex
import subprocess import subprocess
@@ -66,18 +66,28 @@ async def bam_copy(base_blendfile: Path, target_blendfile: Path,
] ]
if exclusion_filter: if exclusion_filter:
if bam_supports_exclude_option():
args.extend(['--exclude', exclusion_filter]) 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) cmd_to_log = ' '.join(shlex.quote(s) for s in args)
log.info('Executing %s', cmd_to_log) log.info('Executing %s', cmd_to_log)
# Workaround for Blender 2.78c not having io_blend_utils.pythonpath()
if hasattr(io_blend_utils, 'pythonpath'):
pythonpath = io_blend_utils.pythonpath()
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( proc = await asyncio.create_subprocess_exec(
*args, *args,
env={'PYTHONPATH': io_blend_utils.pythonpath()}, env=env,
stdin=subprocess.DEVNULL, stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,

View File

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

View File

@@ -112,6 +112,10 @@ 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."""
# 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') self.report({'INFO'}, 'Communicating with Blender Cloud')
try: try:

View File

@@ -44,6 +44,12 @@ def load_wheel(module_name, fname_prefix):
module_name, module.__file__, fname_prefix) module_name, module.__file__, fname_prefix)
return return
sys.path.append(wheel_filename(fname_prefix))
module = __import__(module_name)
log.debug('Loaded %s from %s', module_name, module.__file__)
def wheel_filename(fname_prefix: str) -> str:
path_pattern = os.path.join(my_dir, '%s*.whl' % fname_prefix) path_pattern = os.path.join(my_dir, '%s*.whl' % fname_prefix)
wheels = glob.glob(path_pattern) wheels = glob.glob(path_pattern)
if not wheels: if not wheels:
@@ -51,9 +57,7 @@ def load_wheel(module_name, fname_prefix):
# If there are multiple wheels that match, load the latest one. # If there are multiple wheels that match, load the latest one.
wheels.sort() wheels.sort()
sys.path.append(wheels[-1]) return wheels[-1]
module = __import__(module_name)
log.debug('Loaded %s from %s', module_name, module.__file__)
def load_wheels(): def load_wheels():

View File

@@ -3,6 +3,7 @@
lockfile==0.12.2 lockfile==0.12.2
pillarsdk==1.6.1 pillarsdk==1.6.1
wheel==0.29.0 wheel==0.29.0
blender-bam==1.1.7
# Secondary requirements: # Secondary requirements:
cffi==1.6.0 cffi==1.6.0

View File

@@ -101,6 +101,11 @@ class BuildWheels(Command):
log.info('Downloading Pillar Python SDK wheel') log.info('Downloading Pillar Python SDK wheel')
self.download_wheel(requirements['pillarsdk']) self.download_wheel(requirements['pillarsdk'])
# Download BAM from pypi. This is required for compatibility with Blender 2.78.
if not list(self.wheels_path.glob('blender_bam*.whl')):
log.info('Downloading BAM wheel')
self.download_wheel(requirements['blender-bam'])
# Build CacheControl. # Build CacheControl.
if not list(self.wheels_path.glob('CacheControl*.whl')): if not list(self.wheels_path.glob('CacheControl*.whl')):
log.info('Building CacheControl in %s', self.cachecontrol_path) log.info('Building CacheControl in %s', self.cachecontrol_path)
@@ -227,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.1', version='1.7.4',
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))