BlenderKit:

-fix arrows not showing on the right side when the search results can be increased still.
-improve tasks_queue. It's now hanging on another blender object(panel definition) to have persistent task queue.
-get separate Author info (should also get Gravatar in future)
-get Profile info in fetch server data
-new Profile panel shows user information
-improve tooltip - random tips, author info(will be further improved)
-improve login UI
- fix swapping of search results
This commit is contained in:
Vilem Duha 2019-04-29 21:30:11 +02:00
parent e16c55a110
commit 5412d45d9e
7 changed files with 242 additions and 45 deletions

View File

@ -83,7 +83,8 @@ def scene_load(context):
ui_props = bpy.context.scene.blenderkitUI
ui_props.assetbar_on = False
ui_props.turn_off = False
preferences = bpy.context.preferences.addons['blenderkit'].preferences
preferences.login_attempt = False
licenses = (
('royalty_free', 'Royalty Free', 'royalty free commercial license'),
@ -205,14 +206,19 @@ def switch_search_results(self, context):
props = s.blenderkitUI
if props.asset_type == 'MODEL':
s['search results'] = s.get('bkit model search')
s['search results orig'] = s.get('bkit model search orig')
elif props.asset_type == 'SCENE':
s['search results'] = s.get('bkit scene search')
s['search results orig'] = s.get('bkit scene search orig')
elif props.asset_type == 'MATERIAL':
s['search results'] = s.get('bkit material search')
s['search results orig'] = s.get('bkit material search orig')
elif props.asset_type == 'TEXTURE':
s['search results'] = s.get('bkit texture search')
s['search results orig'] = s.get('bkit texture search orig')
elif props.asset_type == 'BRUSH':
s['search results'] = s.get('bkit brush search')
s['search results orig'] = s.get('bkit brush search orig')
search.load_previews()
@ -1313,9 +1319,8 @@ class BlenderKitAddonPreferences(AddonPreferences):
layout = self.layout
if self.api_key.strip() == '':
op = layout.operator("wm.url_open", text="Register online and get your API Key",
icon='QUESTION')
op.url = paths.BLENDERKIT_SIGNUP_URL
layout.operator("wm.blenderkit_login", text="Login/ Sign up",
icon='URL')
layout.prop(self, "api_key", text='Your API Key')
# layout.label(text='After you paste API Key, categories are downloaded, so blender will freeze for a few seconds.')
layout.prop(self, "global_dir")

View File

@ -25,6 +25,7 @@ from urllib.parse import parse_qs, urlparse
import requests
import threading
import blenderkit
from blenderkit import tasks_queue, utils, paths
CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F"
@ -99,8 +100,8 @@ def login_thread():
def login():
authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
auth_token, refresh_token = authenticator.get_new_token()
print('tokens retrieved')
tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
utils.p('tokens retrieved')
tasks_queue.add_task((write_tokens , (auth_token, refresh_token)))
def refresh_token_thread():
@ -113,11 +114,12 @@ def refresh_token_thread():
def refresh_token(api_key_refresh):
authenticator = SimpleOAuthAuthenticator(server_url=paths.get_bkit_url(), client_id=CLIENT_ID, ports=PORTS)
auth_token, refresh_token = authenticator.get_refreshed_token(api_key_refresh)
tasks_queue.tasks_queue.put('blenderkit.oauth.write_tokens("%s", "%s")' % (auth_token, refresh_token))
if auth_token is not None and refresh_token is not None:
tasks_queue.add_task((blenderkit.oauth.write_tokens , (auth_token, refresh_token)))
def write_tokens(auth_token, refresh_token):
print('writing tokens?')
utils.p('writing tokens?')
preferences = bpy.context.preferences.addons['blenderkit'].preferences
preferences.api_key = auth_token
preferences.api_key_refresh = refresh_token
@ -144,6 +146,25 @@ class RegisterLoginOnline(bpy.types.Operator):
return {'FINISHED'}
class Logout(bpy.types.Operator):
"""Bring linked object hierarchy to scene and make it editable."""
bl_idname = "wm.blenderkit_logout"
bl_label = "BlenderKit logout"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
preferences = bpy.context.preferences.addons['blenderkit'].preferences
preferences.login_attempt = False
preferences.api_key_refresh = ''
preferences.api_key = ''
return {'FINISHED'}
class CancelLoginOnline(bpy.types.Operator):
"""Cancel login attempt."""
@ -163,6 +184,7 @@ class CancelLoginOnline(bpy.types.Operator):
classess = (
RegisterLoginOnline,
CancelLoginOnline,
Logout,
)

View File

@ -25,8 +25,9 @@ if "bpy" in locals():
imp.reload(ui)
imp.reload(version_checker)
imp.reload(oauth)
imp.reload(tasks_queue)
else:
from blenderkit import paths, utils, categories, ui, oauth, version_checker
from blenderkit import paths, utils, categories, ui, oauth, version_checker, tasks_queue
import blenderkit
from bpy.app.handlers import persistent
@ -91,6 +92,7 @@ def fetch_server_data():
api_key = user_preferences.api_key
# version_checker.check_version_thread(url, api_key, blenderkit)
oauth.refresh_token_thread()
get_profile()
categories.fetch_categories_thread(api_key)
@ -177,7 +179,9 @@ def timer_update(): # TODO might get moved to handle all blenderkit stuff.
'tooltip': tooltip,
'tags': r['tags'],
'can_download': r.get('canDownload', True),
'verification_status': r['verificationStatus']
'verification_status': r['verificationStatus'],
'author_id': str(r['author']['id'])
# 'author': r['author']['firstName'] + ' ' + r['author']['lastName']
# 'description': r['description'],
# 'author': r['description'],
}
@ -217,6 +221,7 @@ def timer_update(): # TODO might get moved to handle all blenderkit stuff.
# results = rdata['results']
s[search_name] = result_field
s['search results'] = result_field
s[search_name + ' orig'] = rdata
s['search results orig'] = rdata
load_previews()
ui_props = bpy.context.scene.blenderkitUI
@ -432,17 +437,35 @@ def generate_tooltip(mdata):
# generator is for both upload preview and search, this is only after search
if mdata.get('versionNumber'):
# t = writeblockm(t, mdata, key='versionNumber', pretext='version')
t += 'author: %s %s\n' % (mdata['author']['firstName'], mdata['author']['lastName'])
# t += '\n'
a_id = mdata['author'].get('id')
if a_id != None:
adata = bpy.context.window_manager['bkit authors'].get(str(a_id))
if adata != None:
t += generate_author_textblock(adata)
at = mdata['assetType']
t += '\n'
return t
def get_random_tip():
if at == 'brush' or at == 'texture':
t += 'click to link %s' % mdata['assetType']
if at == 'model' or at == 'material':
t += 'click or drag in scene to link/append %s' % mdata['assetType']
tips = ['Click or drag in scene to link/append %s' % mdata['assetType'],
# "'A' key to search assets by same author",
"'W' key to open Authors webpage",
]
tip = 'Tip: ' + random.choice(tips)
t = writeblock(t, tip)
def generate_author_textblock(adata):
t = ''
if adata not in (None, ''):
t += 'author: %s %s\n' % (adata['firstName'], adata['lastName'])
t = writeblockm(t, adata, key='aboutMe', pretext='')
t += '\n'
t = writeblockm(t, adata, key='aboutMeUrl', pretext='')
return t
@ -492,6 +515,80 @@ class ThumbDownloader(threading.Thread):
# f.write(chunk)
def write_author(a_id, adata):
utils.p('writing author back')
authors = bpy.context.window_manager['bkit authors']
if authors.get(a_id) in (None, ''):
adata['tooltip'] = generate_author_textblock
authors[a_id] = adata
def fetch_author(a_id, api_key):
utils.p('fetch author')
try:
a_url = paths.get_api_url() + 'accounts/' + a_id + '/'
headers = utils.get_headers(api_key)
r = requests.get(a_url, headers=headers)
adata = r.json()
if not hasattr(adata, 'id'):
utils.p(adata)
# utils.p(adata)
tasks_queue.add_task((write_author, (a_id, adata)))
except Exception as e:
utils.p(e)
utils.p('finish fetch')
def get_author(r):
a_id = str(r['author']['id'])
preferences = bpy.context.preferences.addons['blenderkit'].preferences
authors = bpy.context.window_manager.get('bkit authors', {})
if authors == {}:
bpy.context.window_manager['bkit authors'] = authors
a = authors.get(a_id)
if a is None or a is '':
authors[a_id] = ''
thread = threading.Thread(target=fetch_author, args=(a_id, preferences.api_key), daemon=True)
thread.start()
return a
def write_profile(adata):
utils.p('writing profile')
utils.p(adata.keys())
adata['user']['sumAssetFilesSize'] = str(round(adata['user']['sumAssetFilesSize'] / 1024 / 1024)) + ' Mb'
adata['user']['sumPrivateAssetFilesSize'] = str(
round(adata['user']['sumPrivateAssetFilesSize'] / 1024 / 1024)) + ' Mb'
adata['user']['remainingPrivateQuota'] = str(round(adata['user']['remainingPrivateQuota'] / 1024 / 1024)) + ' Mb'
bpy.context.window_manager['bkit profile'] = adata
def fetch_profile(api_key):
utils.p('fetch profile')
try:
a_url = paths.get_api_url() + 'me/'
headers = utils.get_headers(api_key)
r = requests.get(a_url, headers=headers)
adata = r.json()
if not hasattr(adata, 'user'):
utils.p(adata)
utils.p('getting profile failed')
return
tasks_queue.add_task((write_profile, (adata,)))
except Exception as e:
utils.p(e)
def get_profile():
preferences = bpy.context.preferences.addons['blenderkit'].preferences
a = bpy.context.window_manager.get('bkit profile')
if a is None:
thread = threading.Thread(target=fetch_profile, args=(preferences.api_key,), daemon=True)
thread.start()
return a
class Searcher(threading.Thread):
query = None
@ -561,10 +658,10 @@ class Searcher(threading.Thread):
urlquery = url + requeststring
try:
# print(urlquery)
# utils.p(urlquery)
r = requests.get(urlquery, headers=headers)
reports = ''
# print(r.text)
# utils.p(r.text)
except requests.exceptions.RequestException as e:
print(e)
reports = e
@ -596,7 +693,7 @@ class Searcher(threading.Thread):
# print('number of results: ', len(rdata.get('results', [])))
if self.stopped():
print('stopping search : ' + query['keywords'])
utils.p('stopping search : ' + query['keywords'])
return
mt('search finished')
@ -607,7 +704,12 @@ class Searcher(threading.Thread):
thumb_full_urls = []
thumb_full_filepaths = []
# END OF PARSING
getting_authors = {}
for d in rdata.get('results', []):
if getting_authors.get(d['author']['id']) is None:
get_author(d)
getting_authors[d['author']['id']] = True
for f in d['files']:
# TODO move validation of published assets to server, too manmy checks here.
if f['fileType'] == 'thumbnail' and f['fileThumbnail'] != None and f['fileThumbnailLarge'] != None:
@ -650,7 +752,7 @@ class Searcher(threading.Thread):
# TODO do the killing/ stopping here! remember threads might have finished inbetween!
if self.stopped():
print('stopping search : ' + query['keywords'])
utils.p('stopping search : ' + query['keywords'])
return
# this loop handles downloading of small thumbnails
@ -669,12 +771,12 @@ class Searcher(threading.Thread):
for tk, thread in threads_copy.items():
if not thread.is_alive():
thread.join()
# print(x)
# utils.p(x)
del (thumb_sml_download_threads[tk])
# print('fetched thumbnail ', i)
# utils.p('fetched thumbnail ', i)
i += 1
if self.stopped():
print('stopping search : ' + query['keywords'])
utils.p('stopping search : ' + query['keywords'])
return
idx = 0
while len(thumb_sml_download_threads) > 0:
@ -686,7 +788,7 @@ class Searcher(threading.Thread):
i += 1
if self.stopped():
print('stopping search : ' + query['keywords'])
utils.p('stopping search : ' + query['keywords'])
return
# start downloading full thumbs in the end
@ -842,7 +944,7 @@ def mt(text):
alltime = time.time() - search_start_time
since_last = time.time() - prev_time
prev_time = time.time()
print(text, alltime, since_last)
utils.p(text, alltime, since_last)
def add_search_process(query, params):
@ -908,7 +1010,7 @@ def search(own=False, category='', get_next=False, free_only=False):
if category != '':
query['category'] = category
# print('searching')
# utils.p('searching')
props.is_searching = True
params = {
@ -925,6 +1027,7 @@ def search(own=False, category='', get_next=False, free_only=False):
def search_update(self, context):
utils.p('search updater')
if self.search_keywords != '':
search()

View File

@ -2,19 +2,37 @@ import bpy
import queue
import blenderkit
from blenderkit import utils
def get_queue():
if not hasattr(bpy.types.VIEW3D_PT_blenderkit_unified, 'task_queue'):
bpy.types.VIEW3D_PT_blenderkit_unified.task_queue = queue.Queue()
return bpy.types.VIEW3D_PT_blenderkit_unified.task_queue
def add_task(task):
q = get_queue()
q.put(task)
tasks_queue = queue.Queue()
def every_2_seconds():
while not tasks_queue.empty():
print('as a task: ')
fstring = tasks_queue.get()
eval(fstring)
q = get_queue()
while not q.empty():
utils.p('as a task: ')
q = bpy.types.VIEW3D_PT_blenderkit_unified.task_queue
task = q.get()
try:
task[0](*task[1])
except Exception as e:
utils.p('task failed:')
print(e)
return 2.0
def register():
bpy.app.timers.register(every_2_seconds)
def unregister():
bpy.app.timers.unregister(every_2_seconds)
bpy.app.timers.unregister(every_2_seconds)

View File

@ -493,6 +493,7 @@ def draw_callback_2d_search(self, context):
# background of asset bar
if not ui_props.dragging:
search_results = s.get('search results')
search_results_orig = s.get('search results orig')
if search_results == None:
return
h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
@ -524,7 +525,7 @@ def draw_callback_2d_search(self, context):
ui_props.thumb_size,
img,
1)
if len(search_results) - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount):
if search_results_orig['count'] - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount):
if ui_props.active_index == -1:
ui_bgl.draw_rect(ui_props.bar_x + ui_props.bar_width - 25,
ui_props.bar_y - ui_props.bar_height, 25,
@ -913,8 +914,8 @@ class AssetBarOperator(bpy.types.Operator):
default="", options={'SKIP_SAVE'})
def search_more(self):
sro = bpy.context.scene.get('search results orig', {})
if sro.get('next') != None:
sro = bpy.context.scene.get('search results orig')
if sro is not None and sro.get('next') is not None:
search.search(get_next=True)
def exit_modal(self):
@ -1311,6 +1312,13 @@ class AssetBarOperator(bpy.types.Operator):
else:
return {'RUNNING_MODAL'}
if event.type == 'A' and ui_props.active_index != -3:
sr = bpy.context.scene['search results']
asset_data = sr[ui_props.active_index]
a = bpy.context.window_manager['bkit authors'].get(asset_data['author_id'])
if a is not None:
if a.get('aboutMeUrl') is not None:
bpy.ops.wm.url_open(url=a['aboutMeUrl'])
if event.type == 'X' and ui_props.active_index != -3:
sr = bpy.context.scene['search results']
asset_data = sr[ui_props.active_index]

View File

@ -44,7 +44,7 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
else:
threshold = 35
maxlines = 3
li =0
li = 0
for l in lines:
while len(l) > threshold:
i = l.rfind(' ', 0, threshold)
@ -54,10 +54,10 @@ def label_multiline(layout, text='', icon='NONE', width=-1):
layout.label(text=l1, icon=icon)
icon = 'NONE'
l = l[i:]
li+=1
li += 1
if li > maxlines:
break;
if li>maxlines:
if li > maxlines:
break;
layout.label(text=l, icon=icon)
icon = 'NONE'
@ -348,7 +348,7 @@ def draw_panel_scene_search(self, context):
# layout.prop(props, "search_style")
# if props.search_style == 'OTHER':
# layout.prop(props, "search_style_other")
#layout.prop(props, "search_engine")
# layout.prop(props, "search_engine")
layout.separator()
draw_panel_categories(self, context)
@ -382,6 +382,45 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
# layout.operator('object.blenderkit_color_corrector')
class VIEW3D_PT_blenderkit_profile(Panel):
bl_category = "BlenderKit"
bl_idname = "VIEW3D_PT_blenderkit_profile"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_label = "Profile"
@classmethod
def poll(cls, context):
return True
def draw(self, context):
# draw asset properties here
layout = self.layout
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.login_attempt:
layout.label(text='Login through browser')
layout.label(text='in progress.')
layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL')
return
if len(user_preferences.api_key) < 20:
layout.operator("wm.blenderkit_login", text="Login/ Sign up",
icon='URL')
else:
me = bpy.context.window_manager.get('bkit profile')
if me is not None:
me = me['user']
layout.label(text='User: %s %s' % (me['firstName'], me['lastName']))
layout.label(text='Email: %s' % (me['email']))
layout.label(text='Public assets sum: %s ' % (me['sumAssetFilesSize']))
layout.label(text='Private assets sum: %s ' % (me['sumPrivateAssetFilesSize']))
layout.label(text='Remaining private storage: %s' % (me['remainingPrivateQuota']))
layout.operator("wm.blenderkit_logout", text="Logout",
icon='URL')
def draw_panel_model_rating(self, context):
o = bpy.context.active_object
op = draw_ratings(self.layout, context) # , props)
@ -524,14 +563,14 @@ class VIEW3D_PT_blenderkit_unified(Panel):
w = context.region.width
if user_preferences.login_attempt:
layout.label(text = 'Login through browser')
layout.label(text = 'in progress.')
layout.operator("wm.blenderkit_login_cancel", text = "Cancel", icon = 'CANCEL')
layout.label(text='Login through browser')
layout.label(text='in progress.')
layout.operator("wm.blenderkit_login_cancel", text="Cancel", icon='CANCEL')
return
if len(user_preferences.api_key) < 20 and user_preferences.asset_counter >-10:
if len(user_preferences.api_key) < 20 and user_preferences.asset_counter > 5:
layout.operator("wm.blenderkit_login", text="Login/ Sign up",
icon='URL')
icon='URL')
# layout.label(text='Paste your API Key:')
# layout.prop(user_preferences, 'api_key', text='')
layout.separator()
@ -759,7 +798,7 @@ classess = (
VIEW3D_PT_blenderkit_unified,
VIEW3D_PT_blenderkit_model_properties,
VIEW3D_PT_blenderkit_downloads,
VIEW3D_PT_blenderkit_profile
)

View File

@ -173,7 +173,6 @@ def load_prefs():
prefs = json.load(s)
user_preferences.api_key = prefs.get('API_key','')
user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
user_preferences.api_key_refresh = prefs.get('API_key_refresh','')
def save_prefs(self, context):
@ -268,6 +267,9 @@ def get_brush_props(context):
return brush.blenderkit
return None
def p(text):
if bpy.app.debug != 0:
print(p)
def pprint(data):
print(json.dumps(data, indent=4, sort_keys=True))