Compare commits
12 Commits
version-1.
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
56fb1ec3df | |||
e93094cb88 | |||
33718a1a35 | |||
db82dbe730 | |||
8d405330ee | |||
66ddc7b47b | |||
2fa8cb4054 | |||
e7b5c75046 | |||
1d93bd9e5e | |||
ac2d0c033c | |||
61fa63eb1d | |||
7022412889 |
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,5 +1,16 @@
|
|||||||
# Blender Cloud changelog
|
# Blender Cloud changelog
|
||||||
|
|
||||||
|
## Version 1.7.3 (in development)
|
||||||
|
|
||||||
|
- 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)
|
||||||
|
|
||||||
|
@@ -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, 3),
|
||||||
'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 '
|
||||||
|
@@ -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:')
|
||||||
|
@@ -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:
|
||||||
|
@@ -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,
|
||||||
|
@@ -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.
|
||||||
|
@@ -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():
|
||||||
|
@@ -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
|
||||||
|
7
setup.py
7
setup.py
@@ -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.3',
|
||||||
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('.'),
|
||||||
|
Reference in New Issue
Block a user