Added downloading of thumbnails too. Still primitive, thumbs are re-downloaded every time and all I/O blocks the Blender UI.
282 lines
9.6 KiB
Python
282 lines
9.6 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
bl_info = {
|
|
"name": "Blender Cloud Texture Browser",
|
|
"author": "Sybren A. Stüvel and Francesco Siddi",
|
|
"version": (0, 1, 0),
|
|
"blender": (2, 77, 0),
|
|
"location": "TO BE DETERMINED",
|
|
"description": "Allows downloading of textures from the Blender Cloud. Requires "
|
|
"the Blender ID addon.",
|
|
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
|
"Scripts/System/BlenderCloud",
|
|
"category": "System",
|
|
"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
|
|
import bpy.utils.previews
|
|
from bpy.types import AddonPreferences, Operator, PropertyGroup, WindowManager
|
|
from bpy.props import PointerProperty, StringProperty, EnumProperty
|
|
|
|
|
|
class BlenderCloudPreferences(AddonPreferences):
|
|
bl_idname = __name__
|
|
|
|
pillar_server = bpy.props.StringProperty(
|
|
name='Blender Cloud Server',
|
|
description='URL of the Blender Cloud backend server',
|
|
default='https://pillar.blender.org:5000/'
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
# Carefully try and import the Blender ID addon
|
|
try:
|
|
import blender_id.profiles as blender_id_profiles
|
|
except ImportError:
|
|
blender_id_profiles = None
|
|
blender_id_profile = None
|
|
else:
|
|
blender_id_profile = blender_id_profiles.get_active_profile()
|
|
|
|
if blender_id_profiles is None:
|
|
blender_id_icon = 'ERROR'
|
|
blender_id_text = "This add-on requires Blender ID"
|
|
blender_id_help = "Make sure that the Blender ID add-on is installed and activated"
|
|
elif not blender_id_profile:
|
|
blender_id_icon = 'ERROR'
|
|
blender_id_text = "You are logged out."
|
|
blender_id_help = "To login, go to the Blender ID add-on preferences."
|
|
else:
|
|
blender_id_icon = 'WORLD_DATA'
|
|
blender_id_text = "You are logged in as %s." % blender_id_profile['username']
|
|
blender_id_help = "To logout or change profile, " \
|
|
"go to the Blender ID add-on preferences."
|
|
|
|
sub = layout.column()
|
|
sub.label(text=blender_id_text, icon=blender_id_icon)
|
|
sub.label(text="* " + blender_id_help)
|
|
|
|
# options for Pillar
|
|
sub = layout.column()
|
|
sub.enabled = blender_id_icon != 'ERROR'
|
|
sub.prop(self, "pillar_server")
|
|
sub.operator("pillar.credentials_update")
|
|
|
|
|
|
class PillarCredentialsUpdate(Operator):
|
|
"""Updates the Pillar URL and tests the new URL."""
|
|
bl_idname = "pillar.credentials_update"
|
|
bl_label = "Update credentials"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
# Only allow activation when the user is actually logged in.
|
|
return cls.is_logged_in(context)
|
|
|
|
@classmethod
|
|
def is_logged_in(cls, context):
|
|
active_user_id = getattr(context.window_manager, 'blender_id_active_profile', None)
|
|
return bool(active_user_id)
|
|
|
|
def execute(self, context):
|
|
# Only allow activation when the user is actually logged in.
|
|
if not self.is_logged_in(context):
|
|
self.report({'ERROR'}, "No active profile found")
|
|
return {'CANCELLED'}
|
|
|
|
# Test the new URL
|
|
endpoint = bpy.context.user_preferences.addons[__name__].preferences.pillar_server
|
|
pillar._pillar_api = None
|
|
try:
|
|
pillar.get_project_uuid('textures') # Just any query will do.
|
|
except Exception as e:
|
|
print(e)
|
|
self.report({'ERROR'}, 'Failed connection to %s' % endpoint)
|
|
return {'FINISHED'}
|
|
|
|
self.report({'INFO'}, 'Updated cloud server address to %s' % endpoint)
|
|
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()
|