Compare commits

..

10 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
8 changed files with 192 additions and 48 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ blender_cloud/wheels/*.whl
/test_*.py
/dist/
/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 = {
'name': 'Blender Cloud',
'author': 'Sybren A. Stüvel and Francesco Siddi',
'version': (1, 3, 2),
'version': (1, 3, 3),
'blender': (2, 77, 0),
'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 '
@@ -74,18 +74,18 @@ def register():
reload_mod('home_project')
blender = reload_mod('blender')
gui = reload_mod('gui')
async_loop = reload_mod('async_loop')
texture_browser = reload_mod('texture_browser')
settings_sync = reload_mod('settings_sync')
image_sharing = reload_mod('image_sharing')
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)
async_loop.setup_asyncio_executor()
async_loop.register()
gui.register()
texture_browser.register()
blender.register()
settings_sync.register()
image_sharing.register()
@@ -109,10 +109,10 @@ def _monkey_patch_requests():
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()
settings_sync.unregister()
blender.unregister()
gui.unregister()
texture_browser.unregister()
async_loop.unregister()

View File

@@ -10,7 +10,7 @@ import bpy
from bpy.types import AddonPreferences, Operator, WindowManager, Scene, PropertyGroup
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 = 'http://localhost:5000/'
@@ -41,7 +41,7 @@ class SyncStatusProperties(PropertyGroup):
('SYNCING', 'SYNCING', 'Synchronising with Blender Cloud.'),
],
name='status',
description='Current status of Blender Sync.',
description='Current status of Blender Sync',
update=redraw)
version = EnumProperty(
@@ -354,8 +354,6 @@ def register():
def unregister():
unload_custom_icons()
gui.unregister()
bpy.utils.unregister_class(PillarCredentialsUpdate)
bpy.utils.unregister_class(BlenderCloudPreferences)
bpy.utils.unregister_class(SyncStatusProperties)

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,
node_type=None) -> list:
node_type=None, max_results=None) -> list:
"""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.
@@ -290,23 +290,34 @@ async def get_nodes(project_uuid: str = None, parent_node_uuid: str = None,
else:
where['node_type'] = {'$in': node_type}
children = await pillar_call(pillarsdk.Node.all, {
'projection': {'name': 1, 'parent': 1, 'node_type': 1,
'properties.order': 1, 'properties.status': 1,
'properties.files': 1,
'properties.content_type': 1, 'picture': 1},
'where': where,
'embed': ['parent']})
params = {'projection': {'name': 1, 'parent': 1, 'node_type': 1, 'properties.order': 1,
'properties.status': 1, 'properties.files': 1,
'properties.content_type': 1, 'picture': 1},
'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']
async def get_texture_projects() -> list:
async def get_texture_projects(max_results=None) -> list:
"""Returns project dicts that contain textures."""
params = {}
# Pagination
if max_results:
params['max_results'] = int(max_results)
try:
children = await pillar_call(pillarsdk.Project.all_from_endpoint,
'/bcloud/texture-libraries')
'/bcloud/texture-libraries',
params=params)
except pillarsdk.ResourceNotFound as ex:
log.warning('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 logging
import threading
import os
import bpy
import bgl
import blf
import os
import pillarsdk
from . import async_loop, pillar, cache
REQUIRED_ROLES_FOR_TEXTURE_BROWSER = {'subscriber', 'demo'}
MOUSE_SCROLL_PIXELS_PER_TICK = 50
icon_width = 128
icon_height = 128
target_item_width = 400
target_item_height = 128
ICON_WIDTH = 128
ICON_HEIGHT = 128
TARGET_ITEM_WIDTH = 400
TARGET_ITEM_HEIGHT = 128
ITEM_MARGIN_X = 5
ITEM_MARGIN_Y = 5
ITEM_PADDING_X = 5
library_path = '/tmp'
library_icons_path = os.path.join(os.path.dirname(__file__), "icons")
@@ -179,11 +183,11 @@ class MenuItem:
bgl.glTexCoord2d(0, 0)
bgl.glVertex2d(self.x + self.icon_margin_x, self.y)
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.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.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.glDisable(bgl.GL_TEXTURE_2D)
bgl.glDisable(bgl.GL_BLEND)
@@ -193,8 +197,8 @@ class MenuItem:
# draw some text
font_id = 0
blf.position(font_id,
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.x + self.icon_margin_x + ICON_WIDTH + self.text_margin_x,
self.y + ICON_HEIGHT * 0.5 - 0.25 * self.text_height, 0)
blf.size(font_id, self.text_height, self.text_width)
blf.draw(font_id, self.label_text)
@@ -227,6 +231,10 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
mouse_x = 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):
# Refuse to start if the file hasn't been saved.
@@ -256,6 +264,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
self.current_display_content = []
self.loaded_images = set()
self._scroll_reset()
context.window.cursor_modal_set('DEFAULT')
async_loop.AsyncModalOperatorMixin.invoke(self, context, event)
@@ -273,6 +282,7 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
async_loop.ensure_async_loop()
if event.type == 'TIMER':
self._scroll_smooth()
context.area.tag_redraw()
return {'RUNNING_MODAL'}
@@ -295,6 +305,18 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
else:
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 selected is None:
# 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('Current BCloud path is %r', self.current_path)
self.clear_images()
self._scroll_reset()
def thumbnail_loading(node, texture_node):
self.add_menu_item(node, None, 'SPINNER', texture_node['name'])
@@ -534,36 +557,54 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
def _draw_browser(self, context):
"""OpenGL drawing code for the BROWSING state."""
margin_x = 5
margin_y = 5
padding_x = 5
window_region = self._window_region(context)
content_width = window_region.width - margin_x * 2
content_height = window_region.height - margin_y * 2
content_width = window_region.width - ITEM_MARGIN_X * 2
content_height = window_region.height - ITEM_MARGIN_Y * 2
content_x = margin_x
content_y = context.area.height - margin_y - target_item_height
content_x = ITEM_MARGIN_X
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_height = target_item_height
item_width = (content_width - (col_count * ITEM_PADDING_X)) / col_count
item_height = TARGET_ITEM_HEIGHT
block_width = item_width + padding_x
block_height = item_height + margin_y
block_width = item_width + ITEM_PADDING_X
block_height = item_height + ITEM_MARGIN_Y
bgl.glEnable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 0.6)
bgl.glRectf(0, 0, window_region.width, window_region.height)
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):
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.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:
font_id = 0
text = "Communicating with Blender Cloud"
@@ -714,6 +755,32 @@ class BlenderCloudBrowser(pillar.PillarOperatorMixin,
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
addon_keymaps = []
@@ -747,5 +814,4 @@ def unregister():
km.keymap_items.remove(kmi)
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},
name='blender_cloud',
description='The Blender Cloud addon allows browsing the Blender Cloud from Blender.',
version='1.3.2',
version='1.3.3',
author='Sybren A. Stüvel',
author_email='sybren@stuvel.eu',
packages=find_packages('.'),