Compare commits

..

12 Commits

Author SHA1 Message Date
e300c32d64 Bumped version to 1.3.3 2016-07-20 11:13:31 +02:00
63eaaf7dc9 Added addon-bundle dir 2016-07-20 10:59:17 +02:00
6fcea9469f Limit scrolling to content area. 2016-07-19 18:13:32 +02:00
61f86d63e0 Scrolling on MacOS X 2016-07-19 18:13:18 +02:00
0d69b1d7ec Removed trailing period from bl_desc 2016-07-19 18:13:09 +02:00
d5139c767e Texture browser: Added scrolling.
You can scroll indefinitely for now. Might fix that in a later commit.
2016-07-15 17:01:24 +02:00
f0d829da49 Renamed some constants to all-caps 2016-07-15 16:59:52 +02:00
a4817259c8 Moved import 2016-07-15 16:56:55 +02:00
f899f6d1ab Started pagination support, but it isn't used yet. 2016-07-15 16:56:39 +02:00
9a0873eea4 Renamed gui.py to texture_browser.py
Also discovered double-unregister of a class, so that fixed an old bug.
Removed the workaround for that bug.
2016-07-15 14:27:42 +02:00
388a059400 Bumped version to 1.3.2 2016-07-15 14:02:01 +02:00
80d2b5b2e7 Move "Share on Cloud" button from image header to menu. 2016-07-15 14:01:21 +02:00
9 changed files with 194 additions and 50 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ blender_cloud/wheels/*.whl
/test_*.py /test_*.py
/dist/ /dist/
/build/ /build/
/addon-bundle/*.zip

52
addon-bundle/README.txt Normal file
View File

@@ -0,0 +1,52 @@
Blender Cloud Addon
===================
Congratulations on downloading the Blender Cloud addon. For your
convenience, we have bundled it with the Blender ID addon.
To use the Blender Cloud addon, perform the following steps:
- Use Blender (File, User Preferences, Addons, Install from file)
to install blender_id-x.x.x.addon.zip
- If you had a previous version of the Blender Cloud addon installed,
restart Blender now.
- Log in with your Blender ID.
- Use Blender to install blender_cloud-x.x.x.addon.zip
If you don't see the addon in the list, enable the Testing
category.
- Press Ctrl+Alt+Shift+A to start the texture browser.
- Visit the User Preferences, Addons panel, to use the Blender Sync
feature.
Support for Blenders not from blender.org
-----------------------------------------
Maybe you use Blender from another source than blender.org, such as an
Ubuntu package. If that is the case, you have to make sure that the
Python package "requests" is installed. On Ubuntu Linux this can be
done with the command
sudo apt-get install python3-requests
On other platforms & distributions this might be different.
Blender uses Python 3.5, so make sure you install the package for the
correct version of Python.
Subscribing to the Blender Cloud
--------------------------------
The Blender Sync feature is free to use for everybody with a Blender
ID account. In order to use the Texture Browser you need to have a
Blender Cloud subscription. If you didn't subscribe yet, go to:
https://cloud.blender.org/join

16
addon-bundle/bundle.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
cd $(dirname $(readlink -f $0))
BCLOUD=$(ls ../dist/blender_cloud-*.addon.zip | tail -n 1)
BID=$(ls ../../../blender-id-addon/dist/blender_id-*.addon.zip | tail -n 1)
cp -va $BCLOUD $BID .
BUNDLE=$(basename $BCLOUD)
BUNDLE=${BUNDLE/.addon.zip/-bundle-UNZIP_ME_FIRST.zip}
zip -9 $BUNDLE $(basename $BCLOUD) $(basename $BID) README.txt
dolphin --select $BUNDLE 2>/dev/null >/dev/null & disown
echo "CREATED: $BUNDLE"

View File

@@ -21,7 +21,7 @@
bl_info = { bl_info = {
'name': 'Blender Cloud', 'name': 'Blender Cloud',
'author': 'Sybren A. Stüvel and Francesco Siddi', 'author': 'Sybren A. Stüvel and Francesco Siddi',
'version': (1, 3, 1), 'version': (1, 3, 3),
'blender': (2, 77, 0), 'blender': (2, 77, 0),
'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser', 'location': 'Addon Preferences panel, and Ctrl+Shift+Alt+A anywhere for texture browser',
'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon ' 'description': 'Texture library browser and Blender Sync. Requires the Blender ID addon '
@@ -74,18 +74,18 @@ def register():
reload_mod('home_project') reload_mod('home_project')
blender = reload_mod('blender') blender = reload_mod('blender')
gui = reload_mod('gui')
async_loop = reload_mod('async_loop') async_loop = reload_mod('async_loop')
texture_browser = reload_mod('texture_browser')
settings_sync = reload_mod('settings_sync') settings_sync = reload_mod('settings_sync')
image_sharing = reload_mod('image_sharing') image_sharing = reload_mod('image_sharing')
else: else:
from . import (blender, gui, async_loop, settings_sync, blendfile, home_project, from . import (blender, texture_browser, async_loop, settings_sync, blendfile, home_project,
image_sharing) image_sharing)
async_loop.setup_asyncio_executor() async_loop.setup_asyncio_executor()
async_loop.register() async_loop.register()
gui.register() texture_browser.register()
blender.register() blender.register()
settings_sync.register() settings_sync.register()
image_sharing.register() image_sharing.register()
@@ -109,10 +109,10 @@ def _monkey_patch_requests():
def unregister(): def unregister():
from . import blender, gui, async_loop, settings_sync, image_sharing from . import blender, texture_browser, async_loop, settings_sync, image_sharing
image_sharing.unregister() image_sharing.unregister()
settings_sync.unregister() settings_sync.unregister()
blender.unregister() blender.unregister()
gui.unregister() texture_browser.unregister()
async_loop.unregister() async_loop.unregister()

View File

@@ -10,7 +10,7 @@ import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty from bpy.props import StringProperty, EnumProperty, PointerProperty, BoolProperty
from . import pillar, gui from . import pillar
PILLAR_SERVER_URL = 'https://cloudapi.blender.org/' PILLAR_SERVER_URL = 'https://cloudapi.blender.org/'
# PILLAR_SERVER_URL = 'http://localhost:5000/' # PILLAR_SERVER_URL = 'http://localhost:5000/'
@@ -41,7 +41,7 @@ class SyncStatusProperties(PropertyGroup):
('SYNCING', 'SYNCING', 'Synchronising with Blender Cloud.'), ('SYNCING', 'SYNCING', 'Synchronising with Blender Cloud.'),
], ],
name='status', name='status',
description='Current status of Blender Sync.', description='Current status of Blender Sync',
update=redraw) update=redraw)
version = EnumProperty( version = EnumProperty(
@@ -354,8 +354,6 @@ def register():
def unregister(): def unregister():
unload_custom_icons() unload_custom_icons()
gui.unregister()
bpy.utils.unregister_class(PillarCredentialsUpdate) bpy.utils.unregister_class(PillarCredentialsUpdate)
bpy.utils.unregister_class(BlenderCloudPreferences) bpy.utils.unregister_class(BlenderCloudPreferences)
bpy.utils.unregister_class(SyncStatusProperties) bpy.utils.unregister_class(SyncStatusProperties)

View File

@@ -312,12 +312,12 @@ def window_menu(self, context):
def register(): def register():
bpy.utils.register_class(PILLAR_OT_image_share) bpy.utils.register_class(PILLAR_OT_image_share)
bpy.types.IMAGE_HT_header.append(image_editor_menu) bpy.types.IMAGE_MT_image.append(image_editor_menu)
bpy.types.INFO_MT_window.append(window_menu) bpy.types.INFO_MT_window.append(window_menu)
def unregister(): def unregister():
bpy.utils.unregister_class(PILLAR_OT_image_share) bpy.utils.unregister_class(PILLAR_OT_image_share)
bpy.types.IMAGE_HT_header.remove(image_editor_menu) bpy.types.IMAGE_MT_image.remove(image_editor_menu)
bpy.types.INFO_MT_window.remove(window_menu) bpy.types.INFO_MT_window.remove(window_menu)

View File

@@ -260,7 +260,7 @@ async def get_project_uuid(project_url: str) -> str:
async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None, async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
node_type=None) -> list: node_type=None, max_results=None) -> list:
"""Gets nodes for either a project or given a parent node. """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. @param project_uuid: the UUID of the project, or None if only querying by parent_node_uuid.
@@ -290,23 +290,34 @@ async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
else: else:
where['node_type'] = {'$in': node_type} where['node_type'] = {'$in': node_type}
children = await pillar_call(pillarsdk.Node.all, { params = {'projection': {'name': 1, 'parent': 1, 'node_type': 1, 'properties.order': 1,
'projection': {'name': 1, 'parent': 1, 'node_type': 1, 'properties.status': 1, 'properties.files': 1,
'properties.order': 1, 'properties.status': 1, 'properties.content_type': 1, 'picture': 1},
'properties.files': 1, 'where': where,
'properties.content_type': 1, 'picture': 1}, 'embed': ['parent']}
'where': where,
'embed': ['parent']}) # Pagination
if max_results:
params['max_results'] = int(max_results)
children = await pillar_call(pillarsdk.Node.all, params)
return children['_items'] return children['_items']
async def get_texture_projects() -> list: async def get_texture_projects(max_results=None) -> list:
"""Returns project dicts that contain textures.""" """Returns project dicts that contain textures."""
params = {}
# Pagination
if max_results:
params['max_results'] = int(max_results)
try: try:
children = await pillar_call(pillarsdk.Project.all_from_endpoint, children = await pillar_call(pillarsdk.Project.all_from_endpoint,
'/bcloud/texture-libraries') '/bcloud/texture-libraries',
params=params)
except pillarsdk.ResourceNotFound as ex: except pillarsdk.ResourceNotFound as ex:
log.warning('Unable to find texture projects: %s', ex) log.warning('Unable to find texture projects: %s', ex)
raise PillarError('Unable to find texture projects: %s' % ex) raise PillarError('Unable to find texture projects: %s' % ex)

View File

@@ -21,21 +21,25 @@
import asyncio import asyncio
import logging import logging
import threading import threading
import os
import bpy import bpy
import bgl import bgl
import blf import blf
import os
import pillarsdk import pillarsdk
from . import async_loop, pillar, cache from . import async_loop, pillar, cache
REQUIRED_ROLES_FOR_TEXTURE_BROWSER = {'subscriber', 'demo'} REQUIRED_ROLES_FOR_TEXTURE_BROWSER = {'subscriber', 'demo'}
MOUSE_SCROLL_PIXELS_PER_TICK = 50
icon_width = 128 ICON_WIDTH = 128
icon_height = 128 ICON_HEIGHT = 128
target_item_width = 400 TARGET_ITEM_WIDTH = 400
target_item_height = 128 TARGET_ITEM_HEIGHT = 128
ITEM_MARGIN_X = 5
ITEM_MARGIN_Y = 5
ITEM_PADDING_X = 5
library_path = '/tmp' library_path = '/tmp'
library_icons_path = os.path.join(os.path.dirname(__file__), "icons") library_icons_path = os.path.join(os.path.dirname(__file__), "icons")
@@ -179,11 +183,11 @@ class MenuItem:
bgl.glTexCoord2d(0, 0) bgl.glTexCoord2d(0, 0)
bgl.glVertex2d(self.x + self.icon_margin_x, self.y) bgl.glVertex2d(self.x + self.icon_margin_x, self.y)
bgl.glTexCoord2d(0, 1) bgl.glTexCoord2d(0, 1)
bgl.glVertex2d(self.x + self.icon_margin_x, self.y + icon_height) bgl.glVertex2d(self.x + self.icon_margin_x, self.y + ICON_HEIGHT)
bgl.glTexCoord2d(1, 1) bgl.glTexCoord2d(1, 1)
bgl.glVertex2d(self.x + self.icon_margin_x + icon_width, self.y + icon_height) bgl.glVertex2d(self.x + self.icon_margin_x + ICON_WIDTH, self.y + ICON_HEIGHT)
bgl.glTexCoord2d(1, 0) bgl.glTexCoord2d(1, 0)
bgl.glVertex2d(self.x + self.icon_margin_x + icon_width, self.y) bgl.glVertex2d(self.x + self.icon_margin_x + ICON_WIDTH, self.y)
bgl.glEnd() bgl.glEnd()
bgl.glDisable(bgl.GL_TEXTURE_2D) bgl.glDisable(bgl.GL_TEXTURE_2D)
bgl.glDisable(bgl.GL_BLEND) bgl.glDisable(bgl.GL_BLEND)
@@ -193,8 +197,8 @@ class MenuItem:
# draw some text # draw some text
font_id = 0 font_id = 0
blf.position(font_id, blf.position(font_id,
self.x + self.icon_margin_x + icon_width + self.text_margin_x, self.x + self.icon_margin_x + ICON_WIDTH + self.text_margin_x,
self.y + icon_height * 0.5 - 0.25 * self.text_height, 0) self.y + ICON_HEIGHT * 0.5 - 0.25 * self.text_height, 0)
blf.size(font_id, self.text_height, self.text_width) blf.size(font_id, self.text_height, self.text_width)
blf.draw(font_id, self.label_text) blf.draw(font_id, self.label_text)
@@ -227,6 +231,10 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
mouse_x = 0 mouse_x = 0
mouse_y = 0 mouse_y = 0
scroll_offset = 0
scroll_offset_target = 0
scroll_offset_max = 0
scroll_offset_space_left = 0
def invoke(self, context, event): def invoke(self, context, event):
# Refuse to start if the file hasn't been saved. # Refuse to start if the file hasn't been saved.
@@ -256,6 +264,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.current_display_content = [] self.current_display_content = []
self.loaded_images = set() self.loaded_images = set()
self._scroll_reset()
context.window.cursor_modal_set('DEFAULT') context.window.cursor_modal_set('DEFAULT')
async_loop.AsyncModalOperatorMixin.invoke(self, context, event) async_loop.AsyncModalOperatorMixin.invoke(self, context, event)
@@ -273,6 +282,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
async_loop.ensure_async_loop() async_loop.ensure_async_loop()
if event.type == 'TIMER': if event.type == 'TIMER':
self._scroll_smooth()
context.area.tag_redraw() context.area.tag_redraw()
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@@ -295,6 +305,18 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
else: else:
context.window.cursor_set('DEFAULT') context.window.cursor_set('DEFAULT')
# Scrolling
if event.type == 'WHEELUPMOUSE':
self._scroll_by(MOUSE_SCROLL_PIXELS_PER_TICK)
context.area.tag_redraw()
elif event.type == 'WHEELDOWNMOUSE':
self._scroll_by(-MOUSE_SCROLL_PIXELS_PER_TICK)
context.area.tag_redraw()
elif event.type == 'TRACKPADPAN':
self._scroll_by(event.mouse_prev_y - event.mouse_y,
smooth=False)
context.area.tag_redraw()
if left_mouse_release: if left_mouse_release:
if selected is None: if selected is None:
# No item clicked, ignore it. # No item clicked, ignore it.
@@ -442,6 +464,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.log.info('Asynchronously downloading previews to %r', thumbnails_directory) self.log.info('Asynchronously downloading previews to %r', thumbnails_directory)
self.log.info('Current BCloud path is %r', self.current_path) self.log.info('Current BCloud path is %r', self.current_path)
self.clear_images() self.clear_images()
self._scroll_reset()
def thumbnail_loading(node, texture_node): def thumbnail_loading(node, texture_node):
self.add_menu_item(node, None, 'SPINNER', texture_node['name']) self.add_menu_item(node, None, 'SPINNER', texture_node['name'])
@@ -534,36 +557,54 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
def _draw_browser(self, context): def _draw_browser(self, context):
"""OpenGL drawing code for the BROWSING state.""" """OpenGL drawing code for the BROWSING state."""
margin_x = 5
margin_y = 5
padding_x = 5
window_region = self._window_region(context) window_region = self._window_region(context)
content_width = window_region.width - margin_x * 2 content_width = window_region.width - ITEM_MARGIN_X * 2
content_height = window_region.height - margin_y * 2 content_height = window_region.height - ITEM_MARGIN_Y * 2
content_x = margin_x content_x = ITEM_MARGIN_X
content_y = context.area.height - margin_y - target_item_height content_y = context.area.height - ITEM_MARGIN_Y - TARGET_ITEM_HEIGHT
col_count = content_width // target_item_width col_count = content_width // TARGET_ITEM_WIDTH
item_width = (content_width - (col_count * padding_x)) / col_count item_width = (content_width - (col_count * ITEM_PADDING_X)) / col_count
item_height = target_item_height item_height = TARGET_ITEM_HEIGHT
block_width = item_width + padding_x block_width = item_width + ITEM_PADDING_X
block_height = item_height + margin_y block_height = item_height + ITEM_MARGIN_Y
bgl.glEnable(bgl.GL_BLEND) bgl.glEnable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 0.6) bgl.glColor4f(0.0, 0.0, 0.0, 0.6)
bgl.glRectf(0, 0, window_region.width, window_region.height) bgl.glRectf(0, 0, window_region.width, window_region.height)
if self.current_display_content: if self.current_display_content:
bottom_y = float('inf')
# The -1 / +2 are for extra rows that are drawn only half at the top/bottom.
first_item_idx = max(0, int(-self.scroll_offset // block_height - 1) * col_count)
items_per_page = int(content_height // item_height + 2) * col_count
last_item_idx = first_item_idx + items_per_page
for item_idx, item in enumerate(self.current_display_content): for item_idx, item in enumerate(self.current_display_content):
x = content_x + (item_idx % col_count) * block_width x = content_x + (item_idx % col_count) * block_width
y = content_y - (item_idx // col_count) * block_height y = content_y - (item_idx // col_count) * block_height - self.scroll_offset
item.update_placement(x, y, item_width, item_height) item.update_placement(x, y, item_width, item_height)
item.draw(highlighted=item.hits(self.mouse_x, self.mouse_y))
if first_item_idx <= item_idx < last_item_idx:
# Only draw if the item is actually on screen.
item.draw(highlighted=item.hits(self.mouse_x, self.mouse_y))
bottom_y = min(y, bottom_y)
bgl.glColor4f(0.24, 0.68, 0.91, 1)
bgl.glRectf(0,
bottom_y - ITEM_MARGIN_Y,
window_region.width,
bottom_y+1 - ITEM_MARGIN_Y)
self.scroll_offset_space_left = window_region.height - bottom_y
self.scroll_offset_max = (self.scroll_offset -
self.scroll_offset_space_left +
0.25 * block_height)
else: else:
font_id = 0 font_id = 0
text = "Communicating with Blender Cloud" text = "Communicating with Blender Cloud"
@@ -714,6 +755,32 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.report({'INFO'}, 'We just started a browser for you.') self.report({'INFO'}, 'We just started a browser for you.')
def _scroll_smooth(self):
diff = self.scroll_offset_target - self.scroll_offset
if diff == 0:
return
if abs(round(diff)) < 1:
self.scroll_offset = self.scroll_offset_target
return
self.scroll_offset += diff * 0.5
def _scroll_by(self, amount, *, smooth=True):
# Slow down scrolling up
if smooth and amount < 0 and -amount > self.scroll_offset_space_left / 4:
amount = -self.scroll_offset_space_left / 4
self.scroll_offset_target = min(0,
max(self.scroll_offset_max,
self.scroll_offset_target + amount))
if not smooth:
self._scroll_offset = self.scroll_offset_target
def _scroll_reset(self):
self.scroll_offset_target = self.scroll_offset = 0
# store keymaps here to access after registration # store keymaps here to access after registration
addon_keymaps = [] addon_keymaps = []
@@ -747,5 +814,4 @@ def unregister():
km.keymap_items.remove(kmi) km.keymap_items.remove(kmi)
addon_keymaps.clear() addon_keymaps.clear()
if 'bl_rna' in BlenderCloudBrowser.__dict__: # <-- check if we already removed! bpy.utils.unregister_class(BlenderCloudBrowser)
bpy.utils.unregister_class(BlenderCloudBrowser)

View File

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