Made HRDi browsing more efficient.

It now uses the thumbnail of the node for each file, instead of trying
to download each file's thumbnail individually.
This commit is contained in:
Sybren A. Stüvel 2016-07-21 12:10:29 +02:00
parent 3ce89ad5f4
commit 3ec1a3d26d
2 changed files with 106 additions and 75 deletions

View File

@ -517,46 +517,6 @@ async def fetch_texture_thumbs(parent_node_uuid: str, desired_size: str,
log.info('fetch_texture_thumbs: Done downloading texture thumbnails') log.info('fetch_texture_thumbs: Done downloading texture thumbnails')
async def fetch_node_thumbs(nodes: list, desired_size: str,
thumbnail_directory: str,
*,
thumbnail_loading: callable,
thumbnail_loaded: callable,
future: asyncio.Future = None):
"""Fetches all thumbnails of a list of texture/hdri nodes.
Uses the picture of the node, falling back to properties.files[0].file.
@param nodes: List of node documents.
@param desired_size: size indicator, from 'sbtmlh'.
@param thumbnail_directory: directory in which to store the downloaded thumbnails.
@param thumbnail_loading: callback function that takes (pillarsdk.Node, pillarsdk.File)
parameters, which is called before a thumbnail will be downloaded. This allows you to
show a "downloading" indicator.
@param thumbnail_loaded: callback function that takes (pillarsdk.Node, pillarsdk.File object,
thumbnail path) parameters, which is called for every thumbnail after it's been downloaded.
@param future: Future that's inspected; if it is not None and cancelled, texture downloading
is aborted.
"""
# Download all thumbnails in parallel.
if is_cancelled(future):
log.warning('fetch_texture_thumbs: Texture downloading cancelled')
return
coros = (download_texture_thumbnail(node, desired_size,
thumbnail_directory,
thumbnail_loading=thumbnail_loading,
thumbnail_loaded=thumbnail_loaded,
future=future)
for node in nodes)
# raises any exception from failed handle_texture_node() calls.
await asyncio.gather(*coros)
log.info('fetch_node_thumbs: Done downloading %i thumbnails', len(nodes))
async def download_texture_thumbnail(texture_node, desired_size: str, async def download_texture_thumbnail(texture_node, desired_size: str,
thumbnail_directory: str, thumbnail_directory: str,
*, *,
@ -622,6 +582,65 @@ async def download_texture_thumbnail(texture_node, desired_size: str,
loop.call_soon_threadsafe(thumbnail_loaded, texture_node, file_desc, thumb_path) loop.call_soon_threadsafe(thumbnail_loaded, texture_node, file_desc, thumb_path)
async def fetch_node_files(node: pillarsdk.Node,
*,
file_doc_loading: callable,
file_doc_loaded: callable,
future: asyncio.Future = None):
"""Fetches all files of a texture/hdri node.
@param node: Node document to fetch all file docs for.
@param file_doc_loading: callback function that takes (file_id, ) parameters,
which is called before a file document will be downloaded. This allows you to
show a "downloading" indicator.
@param file_doc_loaded: callback function that takes (file_id, pillarsdk.File object)
parameters, which is called for every thumbnail after it's been downloaded.
@param future: Future that's inspected; if it is not None and cancelled, texture downloading
is aborted.
"""
# Download all thumbnails in parallel.
if is_cancelled(future):
log.warning('fetch_texture_thumbs: Texture downloading cancelled')
return
coros = (download_file_doc(file_ref.file,
file_doc_loading=file_doc_loading,
file_doc_loaded=file_doc_loaded,
future=future)
for file_ref in node.properties.files)
# raises any exception from failed handle_texture_node() calls.
await asyncio.gather(*coros)
log.info('fetch_node_files: Done downloading %i files', len(node.properties.files))
async def download_file_doc(file_id,
*,
file_doc_loading: callable,
file_doc_loaded: callable,
future: asyncio.Future = None):
if is_cancelled(future):
log.debug('fetch_texture_thumbs cancelled before finding File for file_id %s', file_id)
return
loop = asyncio.get_event_loop()
# Load the File that belongs to this texture node's picture.
loop.call_soon_threadsafe(file_doc_loading, file_id)
file_desc = await pillar_call(pillarsdk.File.find, file_id, params={
'projection': {'filename': 1, 'variations': 1, 'width': 1, 'height': 1,
'length': 1},
})
if file_desc is None:
log.warning('Unable to find File for file_id %s', file_id)
loop.call_soon_threadsafe(file_doc_loaded, file_id, file_desc)
async def download_file_by_uuid(file_uuid, async def download_file_by_uuid(file_uuid,
target_directory: str, target_directory: str,
metadata_directory: str, metadata_directory: str,

View File

@ -71,20 +71,23 @@ class ProjectNode(SpecialFolderNode):
class HdriFileNode(SpecialFolderNode): class HdriFileNode(SpecialFolderNode):
NODE_TYPE = 'HDRI_FILE' NODE_TYPE = 'HDRI_FILE'
def __init__(self, hdri_node, file_idx): def __init__(self, hdri_node, file_id):
super().__init__() super().__init__()
assert isinstance(hdri_node, pillarsdk.Node), \ assert isinstance(hdri_node, pillarsdk.Node), \
'wrong type for hdri_node: %r' % type(hdri_node) 'wrong type for hdri_node: %r' % type(hdri_node)
self.merge(hdri_node.to_dict()) self.merge(hdri_node.to_dict())
self['node_type'] = self.NODE_TYPE self.node_type = self.NODE_TYPE
self['picture'] = None # force the download to use the files. self.picture = None # force the download to use the files.
# Just represent that one file. # Just represent that one file.
my_file = self['properties']['files'][file_idx] my_file = next(file_ref for file_ref in self['properties']['files']
self['properties']['files'] = [my_file] if file_ref.file == file_id)
self['resolution'] = my_file['resolution']
self.properties.files = [my_file]
self.resolution = my_file['resolution']
self.file = file_id
class MenuItem: class MenuItem:
@ -258,11 +261,14 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
# This contains a stack of Node objects that lead up to the currently browsed node. # This contains a stack of Node objects that lead up to the currently browsed node.
path_stack = [] path_stack = []
# This contains a stack of MenuItem objects that lead up to the currently browsed node.
menu_item_stack = []
timer = None timer = None
log = logging.getLogger('%s.BlenderCloudBrowser' % __name__) log = logging.getLogger('%s.BlenderCloudBrowser' % __name__)
_menu_item_lock = threading.Lock() _menu_item_lock = threading.Lock()
current_display_content = [] current_display_content = [] # list of MenuItems currently displayed
loaded_images = set() loaded_images = set()
thumbnails_cache = '' thumbnails_cache = ''
maximized_area = False maximized_area = False
@ -361,7 +367,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
if selected.is_folder: if selected.is_folder:
self.descend_node(selected.node) self.descend_node(selected)
else: else:
if selected.file_desc is None: if selected.file_desc is None:
# This can happen when the thumbnail information isn't loaded yet. # This can happen when the thumbnail information isn't loaded yet.
@ -399,12 +405,13 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self._state = 'PLEASE_SUBSCRIBE' self._state = 'PLEASE_SUBSCRIBE'
bpy.context.window.cursor_set('HAND') bpy.context.window.cursor_set('HAND')
def descend_node(self, node): def descend_node(self, menu_item: MenuItem):
"""Descends the node hierarchy by visiting this node. """Descends the node hierarchy by visiting this menu item's node.
Also keeps track of the current node, so that we know where the "up" button should go. Also keeps track of the current node, so that we know where the "up" button should go.
""" """
node = menu_item.node
assert isinstance(node, pillarsdk.Node), 'Wrong type %s' % node assert isinstance(node, pillarsdk.Node), 'Wrong type %s' % node
if isinstance(node, UpNode): if isinstance(node, UpNode):
@ -413,6 +420,8 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.current_path = self.current_path.parent self.current_path = self.current_path.parent
if self.path_stack: if self.path_stack:
self.path_stack.pop() self.path_stack.pop()
if self.menu_item_stack:
self.menu_item_stack.pop()
if not self.path_stack: if not self.path_stack:
self.project_name = '' self.project_name = ''
else: else:
@ -423,6 +432,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.current_path /= node['_id'] self.current_path /= node['_id']
self.log.debug('Going down to %r', self.current_path) self.log.debug('Going down to %r', self.current_path)
self.path_stack.append(node) self.path_stack.append(node)
self.menu_item_stack.append(menu_item)
self.browse_assets() self.browse_assets()
@ -504,22 +514,6 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.clear_images() self.clear_images()
self._scroll_reset() self._scroll_reset()
def thumbnail_loading(node, texture_node):
self.add_menu_item(node, None, 'SPINNER', texture_node['name'])
def thumbnail_loaded(node, file_desc, thumb_path):
self.update_menu_item(node, file_desc, thumb_path)
def hdri_thumbnail_loading(node, texture_node):
self.add_menu_item(node, None, 'SPINNER',
'Resolution: %s' % node.resolution)
def hdri_thumbnail_loaded(node, file_desc, thumb_path):
filesize = utils.sizeof_fmt(file_desc.length)
self.update_menu_item(node, file_desc, thumb_path,
'Resolution: %s (%s)' % (node.resolution, filesize))
project_uuid = self.current_path.project_uuid project_uuid = self.current_path.project_uuid
node_uuid = self.current_path.node_uuid node_uuid = self.current_path.node_uuid
is_hdri_node = False is_hdri_node = False
@ -572,20 +566,38 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
if is_hdri_node: if is_hdri_node:
self.log.debug('This is a HDRi node') self.log.debug('This is a HDRi node')
# Construct a fake node for every file in the HDRi.
nodes = []
for file_idx, file_ref in enumerate(current_node.properties.files):
node = HdriFileNode(current_node, file_idx)
nodes.append(node)
await pillar.fetch_node_thumbs(nodes, 's', directory, nodes_for_file_ids = {}
thumbnail_loading=hdri_thumbnail_loading, thumb_path = self.menu_item_stack[-1].thumb_path # Take it off the parent
thumbnail_loaded=hdri_thumbnail_loaded,
def file_doc_loading(file_id):
# Construct a fake node for every file in the HDRi
node = HdriFileNode(current_node, file_id)
nodes_for_file_ids[file_id] = node
self.add_menu_item(node, None, 'SPINNER',
'Resolution: %s' % node.resolution)
def file_doc_loaded(file_id, file_desc):
filesize = utils.sizeof_fmt(file_desc.length)
node = nodes_for_file_ids[file_id]
self.update_menu_item(node, file_desc, thumb_path,
'Resolution: %s (%s)' % (node.resolution, filesize))
await pillar.fetch_node_files(current_node,
file_doc_loading=file_doc_loading,
file_doc_loaded=file_doc_loaded,
future=self.signalling_future) future=self.signalling_future)
self.log.debug('Constructed %i HDRi children', len(current_node.properties.files)) self.log.debug('Constructed %i HDRi children', len(current_node.properties.files))
else: else:
self.log.debug('Fetching texture thumbnails for node %r', node_uuid) self.log.debug('Fetching texture thumbnails for node %r', node_uuid)
def thumbnail_loading(node, texture_node):
self.add_menu_item(node, None, 'SPINNER', texture_node['name'])
def thumbnail_loaded(node, file_desc, thumb_path):
self.update_menu_item(node, file_desc, thumb_path)
await pillar.fetch_texture_thumbs(node_uuid, 's', directory, await pillar.fetch_texture_thumbs(node_uuid, 's', directory,
thumbnail_loading=thumbnail_loading, thumbnail_loading=thumbnail_loading,
thumbnail_loaded=thumbnail_loaded, thumbnail_loaded=thumbnail_loaded,