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:
parent
3ce89ad5f4
commit
3ec1a3d26d
@ -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,
|
||||||
|
@ -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,
|
||||||
|
Reference in New Issue
Block a user