Compare commits

..

24 Commits

Author SHA1 Message Date
5f5f0d8db9 Prevent double map types in the filename. 2016-05-20 16:20:33 +02:00
30f71ac9fc Fixed typo. I'm a moron. 2016-05-20 14:34:35 +02:00
bdef942b0b Replaced log.warning with debug msg.
We can now list all available projects, so there is no need to warn.
2016-05-20 11:33:26 +02:00
2a0ef39b12 Bumped SDK requirement to 1.2.0 2016-05-20 11:31:45 +02:00
c57a3bc902 Bumped version to 1.1.0 2016-05-18 16:36:56 +02:00
b94998d12e Fall back on texture.properties.files[0].file if texture.picture doesn't exist. 2016-05-18 16:27:04 +02:00
1cd42e246e Use current_path in log 2016-05-18 15:55:08 +02:00
079689a532 Client-side sorting of nodes.
The sorting happens after obtaining the individual nodes, as this is done
in parallel in unpredictable order.
2016-05-18 15:13:44 +02:00
597ba6de1c Use project name in download path, rather than UUID.
Filenames are now also sanitized.
2016-05-18 15:13:29 +02:00
7b59391872 Place map type (col, spec, etc) at end of filename instead of start. 2016-05-18 14:14:38 +02:00
8201ba7691 Fix node type name 2016-05-18 14:12:21 +02:00
8f2b0f8faa Allow querying for multiple node types. 2016-05-18 14:11:49 +02:00
33b52cc8a9 CPU-friendlier by lowering fixed redraw rate.
The GUI is still redrawn on other events, such as mouse move, so it still
responds quickly to that. This is just regarding background updates of the
data model, such as when loading thumbnails.
2016-05-18 13:01:48 +02:00
be46b9cf81 Handling more cases of login/credentials issues 2016-05-18 13:01:04 +02:00
ba4c951d32 Use /bcloud/texture-library end point to fetch texture library projects. 2016-05-18 12:50:51 +02:00
5c7343f8c9 Make sure we can always go up again (except at top level) 2016-05-18 12:17:07 +02:00
64d36818fe Start browsing at project overview, instead of inside one project.
Also moved from using project_uuid and node_uuid to using CloudPath
objects.
2016-05-18 11:57:36 +02:00
07f28d3072 Debug log reason why module can't be imported.
Usually this will be because someone just wants to use the wheel, but
during development this can be caused by other issues, and shouldn't
be silenced.
2016-05-18 11:57:36 +02:00
48ca91a364 Skip nodes of unsupported node_type (instead of raising exception) 2016-05-17 17:30:57 +02:00
7ee052f71b Use project UUID from prefs 2016-05-17 17:30:38 +02:00
2bb859efd9 Increased pillarsdk required version 2016-05-10 15:04:49 +02:00
ac3943fe6c Bumped version to 1.0.1 2016-05-10 15:01:15 +02:00
5eaee872bf Added check for user's roles -- disallow usage by non-subscribers.
This makes it clear from the get-go that users need to subscribe. Otherwise
they'll get unexpected errors once they try to download something.
2016-05-10 14:52:51 +02:00
6ce4399407 Show default mouse cursor, instead of the one belonging to the editor. 2016-05-10 14:33:02 +02:00
7 changed files with 251 additions and 104 deletions

View File

@@ -21,7 +21,7 @@
bl_info = {
'name': 'Blender Cloud Texture Browser',
'author': 'Sybren A. Stüvel and Francesco Siddi',
'version': (0, 2, 0),
'version': (1, 1, 0),
'blender': (2, 77, 0),
'location': 'Ctrl+Shift+Alt+A anywhere',
'description': 'Allows downloading of textures from the Blender Cloud. Requires '

View File

@@ -30,14 +30,6 @@ class BlenderCloudPreferences(AddonPreferences):
get=lambda self: PILLAR_SERVER_URL
)
# TODO: Move to the Scene properties?
project_uuid = bpy.props.StringProperty(
name='Project UUID',
description='UUID of the current Blender Cloud project',
default='5672beecc0261b2005ed1a33',
get=lambda self: '5672beecc0261b2005ed1a33'
)
local_texture_dir = StringProperty(
name='Default Blender Cloud texture storage directory',
subtype='DIR_PATH',
@@ -152,16 +144,12 @@ def register():
bpy.utils.register_class(BlenderCloudPreferences)
bpy.utils.register_class(PillarCredentialsUpdate)
WindowManager.blender_cloud_project = StringProperty(
name="Blender Cloud project UUID",
default='5672beecc0261b2005ed1a33') # TODO: don't hard-code this
WindowManager.blender_cloud_node = StringProperty(
name="Blender Cloud node UUID",
default='') # empty == top-level of project
addon_prefs = preferences()
WindowManager.last_blender_cloud_location = StringProperty(
name="Last Blender Cloud browser location",
default="/")
def default_if_empty(scene, context):
"""The scene's local_texture_dir, if empty, reverts to the addon prefs."""

View File

@@ -44,13 +44,27 @@ library_path = '/tmp'
library_icons_path = os.path.join(os.path.dirname(__file__), "icons")
class UpNode(pillarsdk.Node):
class SpecialFolderNode(pillarsdk.Node):
pass
class UpNode(SpecialFolderNode):
def __init__(self):
super().__init__()
self['_id'] = 'UP'
self['node_type'] = 'UP'
class ProjectNode(SpecialFolderNode):
def __init__(self, project):
super().__init__()
assert isinstance(project, pillarsdk.Project), 'wrong type for project: %r' % type(project)
self.merge(project.to_dict())
self['node_type'] = 'PROJECT'
class MenuItem:
"""GUI menu item for the 3D View GUI."""
@@ -66,19 +80,30 @@ class MenuItem:
'SPINNER': os.path.join(library_icons_path, 'spinner.png'),
}
SUPPORTED_NODE_TYPES = {'UP', 'group_texture', 'texture'}
SUPPORTED_NODE_TYPES = {'UP', 'PROJECT', 'group_texture', 'texture'}
def __init__(self, node, file_desc, thumb_path: str, label_text):
self.log = logging.getLogger('%s.MenuItem' % __name__)
if node['node_type'] not in self.SUPPORTED_NODE_TYPES:
self.log.info('Invalid node type in node: %s', node)
raise TypeError('Node of type %r not supported; supported are %r.' % (
node.group_texture, self.SUPPORTED_NODE_TYPES))
node['node_type'], self.SUPPORTED_NODE_TYPES))
assert isinstance(node, pillarsdk.Node), 'wrong type for node: %r' % type(node)
assert isinstance(node['_id'], str), 'wrong type for node["_id"]: %r' % type(node['_id'])
self.node = node # pillarsdk.Node, contains 'node_type' key to indicate type
self.file_desc = file_desc # pillarsdk.File object, or None if a 'folder' node.
self.label_text = label_text
self._thumb_path = ''
self.icon = None
self._is_folder = node['node_type'] == 'group_texture' or isinstance(node, UpNode)
self._is_folder = (node['node_type'] == 'group_texture' or
isinstance(node, SpecialFolderNode))
# Determine sorting order.
# by default, sort all the way at the end and folders first.
self._order = 0 if self._is_folder else 10000
if node and node.properties and node.properties.order is not None:
self._order = node.properties.order
self.thumb_path = thumb_path
@@ -88,6 +113,10 @@ class MenuItem:
self.width = 0
self.height = 0
def sort_key(self):
"""Key for sorting lists of MenuItems."""
return self._order, self.label_text
@property
def thumb_path(self) -> str:
return self._thumb_path
@@ -184,12 +213,10 @@ class BlenderCloudBrowser(bpy.types.Operator):
_state = 'INITIALIZING'
project_uuid = '5672beecc0261b2005ed1a33' # Blender Cloud project UUID
node = None # The Node object we're currently showing, or None if we're at the project top.
node_uuid = '' # Blender Cloud node UUID we're currently showing, i.e. None-safe self.node['_id']
current_path = pillar.CloudPath('/')
project_name = ''
# This contains a stack of Node objects that lead up to the currently browsed node.
# This allows us to display the "up" item.
path_stack = []
async_task = None # asyncio task for fetching thumbnails
@@ -198,7 +225,6 @@ class BlenderCloudBrowser(bpy.types.Operator):
log = logging.getLogger('%s.BlenderCloudBrowser' % __name__)
_menu_item_lock = threading.Lock()
current_path = ''
current_display_content = []
loaded_images = set()
thumbnails_cache = ''
@@ -215,9 +241,9 @@ class BlenderCloudBrowser(bpy.types.Operator):
return {'CANCELLED'}
wm = context.window_manager
self.project_uuid = wm.blender_cloud_project
self.node_uuid = wm.blender_cloud_node
self.path_stack = []
self.current_path = pillar.CloudPath(wm.last_blender_cloud_location)
self.path_stack = [] # list of nodes that make up the current path.
self.thumbnails_cache = cache.cache_directory('thumbnails')
self.mouse_x = event.mouse_x
@@ -237,8 +263,9 @@ class BlenderCloudBrowser(bpy.types.Operator):
self.loaded_images = set()
self.check_credentials()
context.window.cursor_modal_set('DEFAULT')
context.window_manager.modal_handler_add(self)
self.timer = context.window_manager.event_timer_add(1 / 30, context.window)
self.timer = context.window_manager.event_timer_add(1 / 15, context.window)
return {'RUNNING_MODAL'}
@@ -268,24 +295,37 @@ class BlenderCloudBrowser(bpy.types.Operator):
self.mouse_x = event.mouse_x
self.mouse_y = event.mouse_y
if self._state == 'BROWSING' and event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
left_mouse_release = event.type == 'LEFTMOUSE' and event.value == 'RELEASE'
if self._state == 'PLEASE_SUBSCRIBE' and left_mouse_release:
self.open_browser_subscribe()
self._finish(context)
return {'FINISHED'}
if self._state == 'BROWSING':
selected = self.get_clicked()
if selected is None:
# No item clicked, ignore it.
return {'RUNNING_MODAL'}
if selected.is_folder:
self.descend_node(selected.node)
if selected:
context.window.cursor_set('HAND')
else:
if selected.file_desc is None:
# This can happen when the thumbnail information isn't loaded yet.
# Just ignore the click for now.
# TODO: think of a way to handle this properly.
return {'RUNNING_MODAL'}
self.handle_item_selection(context, selected)
context.window.cursor_set('DEFAULT')
elif event.type in {'RIGHTMOUSE', 'ESC'}:
if left_mouse_release:
if selected is None:
# No item clicked, ignore it.
return {'RUNNING_MODAL'}
if selected.is_folder:
self.descend_node(selected.node)
else:
if selected.file_desc is None:
# This can happen when the thumbnail information isn't loaded yet.
# Just ignore the click for now.
# TODO: think of a way to handle this properly.
self.log.debug('Selected item %r has no file_desc', selected)
return {'RUNNING_MODAL'}
self.handle_item_selection(context, selected)
if event.type in {'RIGHTMOUSE', 'ESC'}:
self._finish(context)
return {'CANCELLED'}
@@ -301,6 +341,10 @@ class BlenderCloudBrowser(bpy.types.Operator):
try:
await pillar.check_pillar_credentials()
except pillar.NotSubscribedToCloudError:
self.log.info('User not subscribed to Blender Cloud.')
self._show_subscribe_screen()
return
except pillar.CredentialsNotSyncedError:
self.log.info('Credentials not synced, re-syncing automatically.')
else:
@@ -310,8 +354,12 @@ class BlenderCloudBrowser(bpy.types.Operator):
try:
await pillar.refresh_pillar_credentials()
except pillar.NotSubscribedToCloudError:
self.log.info('User is not a Blender Cloud subscriber.')
self._show_subscribe_screen()
return
except pillar.UserNotLoggedInError:
self.error('User not logged in on Blender ID.')
self.log.error('User not logged in on Blender ID.')
else:
self.log.info('Credentials refreshed and ok, browsing assets.')
await self.async_download_previews()
@@ -320,27 +368,45 @@ class BlenderCloudBrowser(bpy.types.Operator):
raise pillar.UserNotLoggedInError()
# self._new_async_task(self._check_credentials())
def _show_subscribe_screen(self):
"""Shows the "You need to subscribe" screen."""
self._state = 'PLEASE_SUBSCRIBE'
bpy.context.window.cursor_set('HAND')
def descend_node(self, node):
"""Descends the node hierarchy by visiting this node.
Also keeps track of the current node, so that we know where the "up" button should go.
"""
# Going up or down?
if self.path_stack and isinstance(node, UpNode):
self.log.debug('Going up, pop the stack; pre-pop stack is %r', self.path_stack)
node = self.path_stack.pop()
assert isinstance(node, pillarsdk.Node), 'Wrong type %s' % node
if isinstance(node, UpNode):
# Going up.
self.log.debug('Going up to %r', self.current_path)
self.current_path = self.current_path.parent
if self.path_stack:
self.path_stack.pop()
if not self.path_stack:
self.project_name = ''
else:
# Going down, keep track of where we were (project top-level is None)
self.path_stack.append(self.node)
self.log.debug('Going up, push the stack; post-push stack is %r', self.path_stack)
# Going down, keep track of where we were
if isinstance(node, ProjectNode):
self.project_name = node['name']
self.current_path /= node['_id']
self.log.debug('Going down to %r', self.current_path)
self.path_stack.append(node)
# Set 'current' to the given node
self.node_uuid = node['_id'] if node else None
self.node = node
self.browse_assets()
@property
def node(self):
if not self.path_stack:
return None
return self.path_stack[-1]
def _stop_async_task(self):
self.log.debug('Stopping async task')
if self.async_task is None:
@@ -378,6 +444,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
context.space_data.draw_handler_remove(self._draw_handle, 'WINDOW')
context.window_manager.event_timer_remove(self.timer)
context.window.cursor_modal_restore()
if self.maximized_area:
bpy.ops.screen.screen_full_area(use_hide_panels=True)
@@ -406,6 +473,8 @@ class BlenderCloudBrowser(bpy.types.Operator):
self.current_display_content.append(menu_item)
self.loaded_images.add(menu_item.icon.filepath_raw)
self.sort_menu()
return menu_item
def update_menu_item(self, node, *args) -> MenuItem:
@@ -421,11 +490,23 @@ class BlenderCloudBrowser(bpy.types.Operator):
else:
raise ValueError('Unable to find MenuItem(node_uuid=%r)' % node_uuid)
self.sort_menu()
def sort_menu(self):
"""Sorts the self.current_display_content list."""
if not self.current_display_content:
return
with self._menu_item_lock:
self.current_display_content.sort(key=MenuItem.sort_key)
async def async_download_previews(self):
self._state = 'BROWSING'
thumbnails_directory = self.thumbnails_cache
self.log.info('Asynchronously downloading previews to %r', thumbnails_directory)
self.log.info('Current BCloud path is %r', self.current_path)
self.clear_images()
def thumbnail_loading(node, texture_node):
@@ -434,50 +515,59 @@ class BlenderCloudBrowser(bpy.types.Operator):
def thumbnail_loaded(node, file_desc, thumb_path):
self.update_menu_item(node, file_desc, thumb_path, file_desc['filename'])
# Download either by group_texture node UUID or by project UUID (which
# shows all top-level nodes)
if self.node_uuid:
self.log.debug('Getting subnodes for parent node %r', self.node_uuid)
children = await pillar.get_nodes(parent_node_uuid=self.node_uuid,
node_type='group_textures')
# Make sure we can go up again.
if self.path_stack:
self.add_menu_item(UpNode(), None, 'FOLDER', '.. up ..')
elif self.project_uuid:
self.log.debug('Getting subnodes for project node %r', self.project_uuid)
children = await pillar.get_nodes(self.project_uuid, '')
project_uuid = self.current_path.project_uuid
node_uuid = self.current_path.node_uuid
if node_uuid:
# Query for sub-nodes of this node.
self.log.debug('Getting subnodes for parent node %r', node_uuid)
children = await pillar.get_nodes(parent_node_uuid=node_uuid,
node_type='group_texture')
elif project_uuid:
# Query for top-level nodes.
self.log.debug('Getting subnodes for project node %r', project_uuid)
children = await pillar.get_nodes(project_uuid=project_uuid,
parent_node_uuid='',
node_type='group_texture')
else:
# TODO: add "nothing here" icon and trigger re-draw
self.log.warning("Not node UUID and no project UUID, I can't do anything!")
# Query for projects
self.log.debug('No node UUID and no project UUID, listing all projects')
children = await pillar.get_texture_projects()
for proj_dict in children:
self.add_menu_item(ProjectNode(proj_dict), None, 'FOLDER', proj_dict['name'])
return
# Make sure we can go up again.
self.add_menu_item(UpNode(), None, 'FOLDER', '.. up ..')
# Download all child nodes
self.log.debug('Iterating over child nodes of %r', self.node_uuid)
self.log.debug('Iterating over child nodes of %r', self.current_path)
for child in children:
# print(' - %(_id)s = %(name)s' % child)
if child['node_type'] not in MenuItem.SUPPORTED_NODE_TYPES:
self.log.debug('Skipping node of type %r', child['node_type'])
continue
self.add_menu_item(child, None, 'FOLDER', child['name'])
# There are only sub-nodes at the project level, no texture nodes,
# so we won't have to bother looking for textures.
if not self.node_uuid:
if not node_uuid:
return
directory = os.path.join(thumbnails_directory, self.project_uuid, self.node_uuid)
directory = os.path.join(thumbnails_directory, project_uuid, node_uuid)
os.makedirs(directory, exist_ok=True)
self.log.debug('Fetching texture thumbnails for node %r', self.node_uuid)
await pillar.fetch_texture_thumbs(self.node_uuid, 's', directory,
self.log.debug('Fetching texture thumbnails for node %r', node_uuid)
await pillar.fetch_texture_thumbs(node_uuid, 's', directory,
thumbnail_loading=thumbnail_loading,
thumbnail_loaded=thumbnail_loaded,
future=self.signalling_future)
def browse_assets(self):
self.log.debug('Browsing assets at project %r node %r', self.project_uuid, self.node_uuid)
self.log.debug('Browsing assets at %r', self.current_path)
self._new_async_task(self.async_download_previews())
def _new_async_task(self, async_task: asyncio.coroutine, future: asyncio.Future=None):
def _new_async_task(self, async_task: asyncio.coroutine, future: asyncio.Future = None):
"""Stops the currently running async task, and starts another one."""
self.log.debug('Setting up a new task %r, so any existing task must be stopped', async_task)
@@ -499,6 +589,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
'BROWSING': self._draw_browser,
'DOWNLOADING_TEXTURE': self._draw_downloading,
'EXCEPTION': self._draw_exception,
'PLEASE_SUBSCRIBE': self._draw_subscribe,
}
if self._state in drawers:
@@ -510,7 +601,7 @@ class BlenderCloudBrowser(bpy.types.Operator):
bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
blf.size(font_id, 20, 72)
blf.position(font_id, 5, 5, 0)
blf.draw(font_id, self._state)
blf.draw(font_id, '%s %s' % (self._state, self.project_name))
bgl.glDisable(bgl.GL_BLEND)
@staticmethod
@@ -641,6 +732,11 @@ class BlenderCloudBrowser(bpy.types.Operator):
blf.draw(font_id, line)
bgl.glDisable(bgl.GL_BLEND)
def _draw_subscribe(self, context):
self._draw_text_on_colour(context,
'Click to subscribe to the Blender Cloud',
(0.0, 0.0, 0.2, 0.6))
def get_clicked(self) -> MenuItem:
for item in self.current_display_content:
@@ -652,11 +748,13 @@ class BlenderCloudBrowser(bpy.types.Operator):
def handle_item_selection(self, context, item: MenuItem):
"""Called when the user clicks on a menu item that doesn't represent a folder."""
from pillarsdk.utils import sanitize_filename
self.clear_images()
self._state = 'DOWNLOADING_TEXTURE'
node_path_components = [node['name'] for node in self.path_stack if node is not None]
local_path_components = [self.project_uuid] + node_path_components + [self.node['name']]
node_path_components = (node['name'] for node in self.path_stack if node is not None)
local_path_components = [sanitize_filename(comp) for comp in node_path_components]
top_texture_directory = bpy.path.abspath(context.scene.local_texture_dir)
local_path = os.path.join(top_texture_directory, *local_path_components)
@@ -689,6 +787,13 @@ class BlenderCloudBrowser(bpy.types.Operator):
future=signalling_future))
self.async_task.add_done_callback(texture_download_completed)
def open_browser_subscribe(self):
import webbrowser
webbrowser.open_new_tab('https://cloud.blender.org/join')
self.report({'INFO'}, 'We just started a browser for you.')
# store keymaps here to access after registration
addon_keymaps = []

View File

@@ -31,14 +31,15 @@ class UserNotLoggedInError(RuntimeError):
"""
def __str__(self):
return 'UserNotLoggedInError'
return self.__class__.__name__
class CredentialsNotSyncedError(UserNotLoggedInError):
"""Raised when the user may be logged in on Blender ID, but has no Blender Cloud token."""
def __str__(self):
return 'CredentialsNotSyncedError'
class NotSubscribedToCloudError(UserNotLoggedInError):
"""Raised when the user may be logged in on Blender ID, but has no Blender Cloud token."""
class PillarError(RuntimeError):
@@ -62,6 +63,8 @@ class CloudPath(pathlib.PurePosixPath):
@property
def project_uuid(self) -> str:
assert self.parts[0] == '/'
if len(self.parts) <= 1:
return None
return self.parts[1]
@property
@@ -71,11 +74,10 @@ class CloudPath(pathlib.PurePosixPath):
@property
def node_uuid(self) -> str:
node_uuids = self.node_uuids
if not node_uuids:
if len(self.parts) <= 2:
return None
return node_uuids[-1]
return self.parts[-1]
@contextmanager
@@ -171,11 +173,24 @@ async def check_pillar_credentials():
if not subclient:
raise CredentialsNotSyncedError()
try:
await get_project_uuid('textures') # Any query will do.
except pillarsdk.UnauthorizedAccess:
pillar_user_id = subclient['subclient_user_id']
if not pillar_user_id:
raise CredentialsNotSyncedError()
try:
db_user = await pillar_call(pillarsdk.User.find, pillar_user_id)
except (pillarsdk.UnauthorizedAccess, pillarsdk.ResourceNotFound):
raise CredentialsNotSyncedError()
roles = db_user.roles
log.debug('User has roles %r', roles)
if not roles or not {'subscriber', 'demo'}.intersection(set(roles)):
# Delete the subclient info. This forces a re-check later, which can
# then pick up on the user's new status.
del profile.subclients[SUBCLIENT_ID]
profile.save_json()
raise NotSubscribedToCloudError()
async def refresh_pillar_credentials():
"""Refreshes the authentication token on Pillar.
@@ -193,11 +208,15 @@ async def refresh_pillar_credentials():
# Create a subclient token and send it to Pillar.
# May raise a blender_id.BlenderIdCommError
blender_id.create_subclient_token(SUBCLIENT_ID, pillar_endpoint)
try:
blender_id.create_subclient_token(SUBCLIENT_ID, pillar_endpoint)
except blender_id.communication.BlenderIdCommError as ex:
log.warning("Unable to create authentication token: %s", ex)
raise CredentialsNotSyncedError()
# Test the new URL
_pillar_api = None
await get_project_uuid('textures') # Any query will do.
await check_pillar_credentials()
async def get_project_uuid(project_url: str) -> str:
@@ -217,7 +236,7 @@ async def get_project_uuid(project_url: str) -> str:
async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
node_type: str = None) -> list:
node_type = None) -> list:
"""Gets nodes for either a project or given a parent node.
@param project_uuid: the UUID of the project, or None if only querying by parent_node_uuid.
@@ -242,7 +261,10 @@ async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
where['project'] = project_uuid
if node_type:
where['node_type'] = node_type
if isinstance(node_type, str):
where['node_type'] = node_type
else:
where['node_type'] = {'$in': node_type}
children = await pillar_call(pillarsdk.Node.all, {
'projection': {'name': 1, 'parent': 1, 'node_type': 1,
@@ -250,12 +272,24 @@ async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
'properties.files': 1,
'properties.content_type': 1, 'picture': 1},
'where': where,
'sort': 'properties.order',
'embed': ['parent']})
return children['_items']
async def get_texture_projects() -> list:
"""Returns project dicts that contain textures."""
try:
children = await pillar_call(pillarsdk.Project.all_from_endpoint,
'/bcloud/texture-libraries')
except pillarsdk.ResourceNotFound as ex:
log.warning('Unable to find texture projects: %s', ex)
raise PillarError('Unable to find texture projects: %s' % ex)
return children['_items']
async def download_to_file(url, filename, *,
header_store: str,
chunk_size=100 * 1024,
@@ -367,7 +401,7 @@ async def fetch_thumbnail_info(file: pillarsdk.File, directory: str, desired_siz
finished.
"""
thumb_link = await pillar_call(file.thumbnail_file, desired_size)
thumb_link = await pillar_call(file.thumbnail, desired_size)
if thumb_link is None:
raise ValueError("File {} has no thumbnail of size {}"
@@ -439,8 +473,22 @@ async def download_texture_thumbnail(texture_node, desired_size: str,
loop = asyncio.get_event_loop()
# Find the File that belongs to this texture node
pic_uuid = texture_node['picture']
# Find out which file to use for the thumbnail picture.
pic_uuid = texture_node.picture
if not pic_uuid:
# Fall back to the first texture file, if it exists.
log.debug('Node %r does not have a picture, falling back to first file.',
texture_node['_id'])
files = texture_node.properties and texture_node.properties.files
if not files:
log.info('Node %r does not have a picture nor files, skipping.', texture_node['_id'])
return
pic_uuid = files[0].file
if not pic_uuid:
log.info('Node %r does not have a picture nor files, skipping.', texture_node['_id'])
return
# Load the File that belongs to this texture node's picture.
loop.call_soon_threadsafe(thumbnail_loading, texture_node, texture_node)
file_desc = await pillar_call(pillarsdk.File.find, pic_uuid, params={
'projection': {'filename': 1, 'variations': 1, 'width': 1, 'height': 1},
@@ -495,8 +543,13 @@ async def download_file_by_uuid(file_uuid,
metadata_file = os.path.join(metadata_directory, 'files', '%s.json' % file_uuid)
save_as_json(file_desc, metadata_file)
file_path = os.path.join(target_directory,
sanitize_filename('%s-%s' % (map_type, file_desc['filename'])))
root, ext = os.path.splitext(file_desc['filename'])
if root.endswith(map_type):
target_filename = '%s%s' % (root, ext)
else:
target_filename = '%s-%s%s' % (root, map_type, ext)
file_path = os.path.join(target_directory, sanitize_filename(target_filename))
file_url = file_desc['link']
# log.debug('Texture %r:\n%s', file_uuid, pprint.pformat(file_desc.to_dict()))
loop.call_soon_threadsafe(file_loading, file_path, file_desc)

View File

@@ -18,8 +18,9 @@ def load_wheel(module_name, fname_prefix):
try:
module = __import__(module_name)
except ImportError:
pass
except ImportError as ex:
log.debug('Unable to import %s directly, will try wheel: %s',
module_name, ex)
else:
log.debug('Was able to load %s from %s, no need to load wheel %s',
module_name, module.__file__, fname_prefix)

View File

@@ -1,7 +1,7 @@
# Primary requirements:
CacheControl==0.11.6
lockfile==0.12.2
pillarsdk==1.0.0
pillarsdk==1.2.0
wheel==0.29.0
# Secondary requirements:

View File

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