Compare commits
12 Commits
version-1.
...
version-1.
Author | SHA1 | Date | |
---|---|---|---|
e300c32d64 | |||
63eaaf7dc9 | |||
6fcea9469f | |||
61f86d63e0 | |||
0d69b1d7ec | |||
d5139c767e | |||
f0d829da49 | |||
a4817259c8 | |||
f899f6d1ab | |||
9a0873eea4 | |||
388a059400 | |||
80d2b5b2e7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
52
addon-bundle/README.txt
Normal 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
16
addon-bundle/bundle.sh
Executable 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"
|
@@ -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()
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
|
2
setup.py
2
setup.py
@@ -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('.'),
|
||||||
|
Reference in New Issue
Block a user