diff options
author | Vilem Duha <vilem.duha@gmail.com> | 2019-04-29 22:30:11 +0300 |
---|---|---|
committer | Vilem Duha <vilem.duha@gmail.com> | 2019-05-19 16:45:48 +0300 |
commit | 5412d45d9e682d0b8fe8437b735ee06979e34f02 (patch) | |
tree | 21deadffbf446aad4c7dd314ce1581a3d445d040 /blenderkit | |
parent | e16c55a110a5cde3bf5c83a156195b5d71481488 (diff) |
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
Diffstat (limited to 'blenderkit')
-rw-r--r-- | blenderkit/__init__.py | 13 | ||||
-rw-r--r-- | blenderkit/oauth.py | 30 | ||||
-rw-r--r-- | blenderkit/search.py | 137 | ||||
-rw-r--r-- | blenderkit/tasks_queue.py | 32 | ||||
-rw-r--r-- | blenderkit/ui.py | 14 | ||||
-rw-r--r-- | blenderkit/ui_panels.py | 59 | ||||
-rw-r--r-- | blenderkit/utils.py | 4 |
7 files changed, 243 insertions, 46 deletions
diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 6a8b1886..929a900a 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -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") diff --git a/blenderkit/oauth.py b/blenderkit/oauth.py index 78c8dfb3..f2cdf3e8 100644 --- a/blenderkit/oauth.py +++ b/blenderkit/oauth.py @@ -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, ) diff --git a/blenderkit/search.py b/blenderkit/search.py index 39a770ee..45865391 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -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() diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py index 08ac90bf..55029753 100644 --- a/blenderkit/tasks_queue.py +++ b/blenderkit/tasks_queue.py @@ -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)
\ No newline at end of file + bpy.app.timers.unregister(every_2_seconds) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index b0ac9209..e26b24ab 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -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] diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 1047eb98..f97a9088 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -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 ) diff --git a/blenderkit/utils.py b/blenderkit/utils.py index cebc2eaf..1a1d4ed7 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -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)) |