Added browsing group_texture nodes

Added downloading of thumbnails too. Still primitive, thumbs are
re-downloaded every time and all I/O blocks the Blender UI.
This commit is contained in:
Sybren A. Stüvel 2016-03-09 17:34:37 +01:00
parent 0f8cededb2
commit 01b73a0439
2 changed files with 188 additions and 14 deletions

View File

@ -32,17 +32,21 @@ bl_info = {
"support": "TESTING"
}
import os.path
import typing
# Support reloading
if 'pillar' in locals():
import importlib
pillar = importlib.reload(pillar)
else:
from . import pillar
import bpy
from bpy.types import AddonPreferences, Operator, PropertyGroup
from bpy.props import PointerProperty, StringProperty
import bpy.utils.previews
from bpy.types import AddonPreferences, Operator, PropertyGroup, WindowManager
from bpy.props import PointerProperty, StringProperty, EnumProperty
class BlenderCloudPreferences(AddonPreferences):
@ -126,13 +130,152 @@ class PillarCredentialsUpdate(Operator):
return {'FINISHED'}
# We can store multiple preview collections here,
# however in this example we only store "main"
preview_collections = {}
def enum_previews_from_directory_items(self, context) -> typing.List[typing.AnyStr]:
"""EnumProperty callback"""
if context is None:
return []
wm = context.window_manager
project_uuid = wm.blender_cloud_project
node_uuid = wm.blender_cloud_node
# Get the preview collection (defined in register func).
pcoll = preview_collections["blender_cloud"]
if pcoll.project_uuid == project_uuid and pcoll.node_uuid == node_uuid:
return pcoll.previews
print('Loading previews for project {!r} node {!r}'.format(project_uuid, node_uuid))
enum_items = []
# If we have a node UUID, we fetch the textures
# FIXME: support mixture of sub-nodes and textures under one node.
if node_uuid:
# Make sure we can go up again.
parent = pillar.parent_node_uuid(node_uuid)
enum_items.append(('node-{}'.format(parent), 'up', 'up',
'FILE_FOLDER',
len(enum_items)))
directory = os.path.join(wm.thumbnails_cache, project_uuid, node_uuid)
os.makedirs(directory, exist_ok=True)
for file_desc, thumb_path in pillar.fetch_texture_thumbs(node_uuid, 's', directory):
thumb = pcoll.get(thumb_path)
if thumb is None:
thumb = pcoll.load(thumb_path, thumb_path, 'IMAGE')
enum_items.append(('thumb-{}'.format(thumb_path), file_desc['filename'],
thumb_path,
# TODO: get something here that allows downloading the texture
thumb.icon_id,
len(enum_items)))
elif project_uuid:
children = pillar.get_nodes(project_uuid, '')
for child in children:
print(' - %(_id)s = %(name)s' % child)
enum_items.append(('node-{}'.format(child['_id']), child['name'],
'description',
'FILE_FOLDER',
len(enum_items)))
pcoll.previews = enum_items
pcoll.project_uuid = project_uuid
pcoll.node_uuid = node_uuid
return pcoll.previews
def enum_previews_from_directory_update(self, context):
print('Updating from {!r}'.format(self.blender_cloud_thumbnails))
sel_type, sel_id = self.blender_cloud_thumbnails.split('-', 1)
if sel_type == 'node':
# Go into this node
self.blender_cloud_node = sel_id
elif sel_type == 'thumb':
# Select this image
pass
else:
print("enum_previews_from_directory_update: Don't know what to do with {!r}"
.format(self.blender_cloud_thumbnails))
class PreviewsExamplePanel(bpy.types.Panel):
"""Creates a Panel in the Object properties window"""
bl_label = "Previews Example Panel"
bl_idname = "OBJECT_PT_previews"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
layout = self.layout
wm = context.window_manager
row = layout.column()
row.prop(wm, "thumbnails_cache")
row.prop(wm, "blender_cloud_project")
row.prop(wm, "blender_cloud_node")
row.template_icon_view(wm, "blender_cloud_thumbnails", show_labels=True)
row.prop(wm, "blender_cloud_thumbnails")
def register():
bpy.utils.register_module(__name__)
WindowManager.thumbnails_cache = StringProperty(
name="Thumbnails cache",
subtype='DIR_PATH',
default='/home/sybren/.cache/blender_cloud/thumbnails')
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
WindowManager.blender_cloud_thumbnails = EnumProperty(
items=enum_previews_from_directory_items,
update=enum_previews_from_directory_update,
)
# Note that preview collections returned by bpy.utils.previews
# are regular Python objects - you can use them to store custom data.
#
# This is especially useful here, since:
# - It avoids us regenerating the whole enum over and over.
# - It can store enum_items' strings
# (remember you have to keep those strings somewhere in py,
# else they get freed and Blender references invalid memory!).
pcoll = bpy.utils.previews.new()
pcoll.previews = ()
pcoll.project_uuid = ''
pcoll.node_uuid = ''
preview_collections["blender_cloud"] = pcoll
def unregister():
bpy.utils.unregister_module(__name__)
del WindowManager.thumbnails_cache
del WindowManager.blender_cloud_project
del WindowManager.blender_cloud_node
del WindowManager.blender_cloud_thumbnails
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
if __name__ == "__main__":
register()

View File

@ -1,6 +1,7 @@
import sys
import os
import concurrent.futures
import functools
# Add our shipped Pillar SDK wheel to the Python path
if not any('pillar_sdk' in path for path in sys.path):
@ -114,35 +115,65 @@ def get_nodes(project_uuid: str = None, parent_node_uuid: str = None) -> list:
return children['_items']
def fetch_texture_thumbs(parent_node_uuid: str, desired_size: str):
"""Fetches all texture thumbnails in a certain parent node.
def fetch_texture_thumbs(parent_node_uuid: str, desired_size: str, thumbnail_directory: str):
"""Generator, fetches all texture thumbnails in a certain parent node.
@param parent_node_uuid: the UUID of the parent node. All sub-nodes will be downloaded.
@param desired_size: size indicator, from 'sbtmlh'.
@param thumbnail_directory: directory in which to store the downloaded thumbnails.
@returns: generator that yields (pillarsdk.File object, thumbnail path) tuples
"""
def fetch_thumbnail_from_node(texture_node: pillarsdk.Node, api: pillarsdk.Api):
api = pillar_api()
def fetch_thumbnail_from_node(texture_node: pillarsdk.Node):
# Fetch the File description JSON
pic_uuid = texture_node['picture']
file_desc = pillarsdk.File.find(pic_uuid, {
'projection': {'filename': 1, 'variations': 1, 'width': 1, 'height': 1},
}, api=api)
if file_desc is None:
print('Unable to find picture {}'.format(pic_uuid))
return None, None
# Save the thumbnail
thumb_path = file_desc.stream_thumb_to_file('/tmp', desired_size, api=api)
thumb_path = file_desc.stream_thumb_to_file(thumbnail_directory, desired_size, api=api)
return texture_node['name'], thumb_path
return file_desc, thumb_path
api = pillar_api()
texture_nodes = (node for node in get_nodes(parent_node_uuid=parent_node_uuid)
if node['node_type'] == 'texture')
# # Single-threaded, not maintained:
# for node in texture_nodes:
# node, file = fetch_thumbnail_from_node(node)
# print('Node {} has picture {}'.format(node, file))
# Multi-threaded:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
# Queue up fetching of thumbnails
futures = [executor.submit(fetch_thumbnail_from_node, node, api)
for node in get_nodes(parent_node_uuid=parent_node_uuid)
if node['node_type'] == 'texture']
futures = [executor.submit(fetch_thumbnail_from_node, node)
for node in texture_nodes]
for future in futures:
node, file = future.result()
print('Node {} has picture {}'.format(node, file))
file_desc, thumb_path = future.result()
yield file_desc, thumb_path
print('Done downloading texture thumbnails')
@functools.lru_cache(128)
def parent_node_uuid(node_uuid: str) -> str:
"""Returns the UUID of the node's parent node, or an empty string if this is the top level."""
api = pillar_api()
node = pillarsdk.Node.find(node_uuid, {'projection': {'parent': 1}}, api=api)
if node is None:
return ''
print('Found node {}'.format(node))
try:
return node['parent']
except KeyError:
return ''