diff options
author | Vilem Duha <vilem.duha@gmail.com> | 2021-10-20 08:53:20 +0300 |
---|---|---|
committer | Vilem Duha <vilem.duha@gmail.com> | 2021-10-20 08:53:31 +0300 |
commit | 79a0f96101e3e098f76ce22a88b320f6c831b7b5 (patch) | |
tree | 6305099ac7e26fe66e626d08735e7412cd445b29 | |
parent | 01a4619f5fbb93de457b9cc15a2cf564f87e2b7a (diff) |
BlenderKit: initial support for notifications
slight optimisation of the new asset bar
-rw-r--r-- | blenderkit/asset_bar_op.py | 42 | ||||
-rw-r--r-- | blenderkit/bkit_oauth.py | 1 | ||||
-rw-r--r-- | blenderkit/bl_ui_widgets/bl_ui_button.py | 5 | ||||
-rw-r--r-- | blenderkit/bl_ui_widgets/bl_ui_image.py | 5 | ||||
-rw-r--r-- | blenderkit/comments_utils.py | 78 | ||||
-rw-r--r-- | blenderkit/icons.py | 1 | ||||
-rw-r--r-- | blenderkit/search.py | 6 | ||||
-rw-r--r-- | blenderkit/thumbnails/bell.png | bin | 0 -> 946 bytes | |||
-rw-r--r-- | blenderkit/ui_panels.py | 119 |
9 files changed, 223 insertions, 34 deletions
diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py index 5011ffa2..2a2ff4e8 100644 --- a/blenderkit/asset_bar_op.py +++ b/blenderkit/asset_bar_op.py @@ -15,7 +15,7 @@ import random import math import blenderkit -from blenderkit import ui, paths, utils, search +from blenderkit import ui, paths, utils, search, comments_utils from bpy.props import ( IntProperty, @@ -55,8 +55,7 @@ def get_area_height(self): BL_UI_Widget.get_area_height = get_area_height - -def asset_bar_modal(self, context, event): +def modal_inside(self,context,event): ui_props = bpy.context.window_manager.blenderkitUI if ui_props.turn_off: ui_props.turn_off = False @@ -110,6 +109,9 @@ def asset_bar_modal(self, context, event): return {"PASS_THROUGH"} +def asset_bar_modal(self, context, event): + return modal_inside(self,context,event) + def asset_bar_invoke(self, context, event): if not self.on_invoke(context, event): @@ -285,6 +287,11 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): for w in self.tooltip_widgets: w.visible = True + def show_notifications(self, widget): + bpy.ops.wm.show_notifications() + if comments_utils.check_notifications_read(): + widget.visible = False + def check_new_search_results(self, context): sr = bpy.context.window_manager.get('search results') if not hasattr(self, 'search_results_count'): @@ -404,15 +411,13 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): self.button_close.set_location(self.bar_width - self.other_button_size, 0) self.button_scroll_up.set_location(self.bar_width, 0) - self.panel.set_location(self.panel.x, self.panel.y) self.panel.width = self.bar_width self.panel.height = self.bar_height + self.panel.set_location(self.panel.x, self.panel.y) + # to hide arrows accordingly self.scroll_update() - # self.init_tooltip() - # self.hide_tooltip() - # self.scroll_update() def asset_button_init(self, asset_x, asset_y, button_idx): ui_scale = bpy.context.preferences.view.ui_scale @@ -460,6 +465,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): progress_bar.bg_color = (0.0, 1.0, 0.0, 0.3) new_button.progress_bar = progress_bar self.progress_bars.append(progress_bar) + + + # if result['downloaded'] > 0: # ui_bgl.draw_rect(x, y, int(ui_props.thumb_size * result['downloaded'] / 100.0), 2, green) @@ -496,14 +504,17 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): self.asset_buttons.append(new_button) button_idx += 1 - self.button_close = BL_UI_Button(self.bar_width - self.other_button_size, -0, self.other_button_size, + self.button_close = BL_UI_Button(self.bar_width - self.other_button_size, -self.other_button_size, self.other_button_size, self.other_button_size) self.button_close.bg_color = button_bg_color self.button_close.hover_bg_color = button_hover_color - self.button_close.text = "X" + self.button_close.text = "" + img_fp = paths.get_addon_thumbnail_path('vs_rejected.png') + self.button_close.set_image(img_fp) self.button_close.set_mouse_down(self.cancel_press) self.widgets_panel.append(self.button_close) + scroll_width = 30 self.button_scroll_down = BL_UI_Button(-scroll_width, 0, scroll_width, self.bar_height) self.button_scroll_down.bg_color = button_bg_color @@ -529,6 +540,19 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): self.widgets_panel.append(self.button_scroll_up) + #notifications + if not comments_utils.check_notifications_read(): + + self.button_notifications = BL_UI_Button(self.bar_width - self.other_button_size, self.bar_height, self.other_button_size, + self.other_button_size) + self.button_notifications.bg_color = button_bg_color + self.button_notifications.hover_bg_color = button_hover_color + self.button_notifications.text = "" + img_fp = paths.get_addon_thumbnail_path('bell.png') + self.button_notifications.set_image(img_fp) + self.button_notifications.set_mouse_down(self.show_notifications) + self.widgets_panel.append(self.button_notifications) + self.update_images() def position_and_hide_buttons(self): diff --git a/blenderkit/bkit_oauth.py b/blenderkit/bkit_oauth.py index e080845a..fa00e616 100644 --- a/blenderkit/bkit_oauth.py +++ b/blenderkit/bkit_oauth.py @@ -91,6 +91,7 @@ def write_tokens(auth_token, refresh_token, oauth_response): props.report = '' ui.add_report('BlenderKit Re-Login success') search.get_profile() + categories.fetch_categories_thread(auth_token, force = False) diff --git a/blenderkit/bl_ui_widgets/bl_ui_button.py b/blenderkit/bl_ui_widgets/bl_ui_button.py index 3f7fe082..d8772e68 100644 --- a/blenderkit/bl_ui_widgets/bl_ui_button.py +++ b/blenderkit/bl_ui_widgets/bl_ui_button.py @@ -68,8 +68,9 @@ class BL_UI_Button(BL_UI_Widget): def set_image(self, rel_filepath): try: - self.__image = bpy.data.images.load(rel_filepath, check_existing=True) - self.__image.gl_load() + if self.__image is None or self.__image.filepath != rel_filepath: + self.__image = bpy.data.images.load(rel_filepath, check_existing=True) + self.__image.gl_load() except: pass diff --git a/blenderkit/bl_ui_widgets/bl_ui_image.py b/blenderkit/bl_ui_widgets/bl_ui_image.py index 7d69ad13..a11c52c5 100644 --- a/blenderkit/bl_ui_widgets/bl_ui_image.py +++ b/blenderkit/bl_ui_widgets/bl_ui_image.py @@ -21,8 +21,9 @@ class BL_UI_Image(BL_UI_Widget): def set_image(self, rel_filepath): try: - self.__image = bpy.data.images.load(rel_filepath, check_existing=True) - self.__image.gl_load() + if self.__image is None or self.__image.filepath != rel_filepath: + self.__image = bpy.data.images.load(rel_filepath, check_existing=True) + self.__image.gl_load() except: pass diff --git a/blenderkit/comments_utils.py b/blenderkit/comments_utils.py index 121beeb1..f91e151f 100644 --- a/blenderkit/comments_utils.py +++ b/blenderkit/comments_utils.py @@ -27,8 +27,10 @@ import logging bk_logger = logging.getLogger('blenderkit') -def upload_comment_thread(url, comment='', headers=None): +def upload_comment_thread(url, comment='', api_key=None): ''' Upload rating thread function / disconnected from blender data.''' + headers = utils.get_headers(api_key) + bk_logger.debug('upload comment ' + comment) # rating_url = url + rating_name + '/' @@ -55,8 +57,10 @@ def upload_comment_thread(url, comment='', headers=None): # print('ratings upload failed: %s' % str(e)) -def upload_comment_flag_thread(url, comment_id='', flag='like', headers=None): +def upload_comment_flag_thread(url, comment_id='', flag='like', api_key=None): ''' Upload rating thread function / disconnected from blender data.''' + headers = utils.get_headers(api_key) + bk_logger.debug('upload comment flag' + str(comment_id)) # rating_url = url + rating_name + '/' @@ -74,10 +78,10 @@ def upload_comment_flag_thread(url, comment_id='', flag='like', headers=None): # print('ratings upload failed: %s' % str(e)) -def send_comment_to_thread(url, comment, headers): +def send_comment_to_thread(url, comment, api_key): '''Sens rating into thread rating, main purpose is for tasks_queue. One function per property to avoid lost data due to stashing.''' - thread = threading.Thread(target=upload_comment_thread, args=(url, comment, headers)) + thread = threading.Thread(target=upload_comment_thread, args=(url, comment, api_key)) thread.start() @@ -97,9 +101,9 @@ def get_comments_local(asset_id): return None -def get_comments(asset_id, headers): +def get_comments(asset_id, api_key): ''' - Retrieve ratings from BlenderKit server. Can be run from a thread + Retrieve comments from BlenderKit server. Can be run from a thread Parameters ---------- asset_id @@ -109,6 +113,8 @@ def get_comments(asset_id, headers): ------- ratings - dict of type:value ratings ''' + headers = utils.get_headers(api_key) + url = paths.get_api_url() + 'comments/assets-uuidasset/' + asset_id + '/' params = {} r = rerequests.get(url, params=params, verify=True, headers=headers) @@ -125,3 +131,63 @@ def get_comments(asset_id, headers): # # store empty ratings too, so that server isn't checked repeatedly # tasks_queue.add_task((store_rating_local_empty,(asset_id,))) # return ratings + +def store_notifications_local(notifications): + bpy.context.window_manager['bkit notifications'] = notifications + +def check_notifications_read(): + '''checks if all notifications were already read, and removes them if so''' + all_read = True + notifications = bpy.context.window_manager.get('bkit notifications') + if notifications is None: + return True + for n in notifications: + if n['unread'] == 1: + all_read = False + return False + bpy.context.window_manager['bkit notifications'] = None + return True + +def get_notifications(api_key): + ''' + Retrieve ratings from BlenderKit server. Can be run from a thread + Parameters + ---------- + asset_id + headers + + Returns + ------- + ratings - dict of type:value ratings + ''' + headers = utils.get_headers(api_key) + + url = paths.get_api_url() + 'notifications/unread/' + params = {} + r = rerequests.get(url, params=params, verify=True, headers=headers) + if r is None: + return + if r.status_code == 200: + rj = r.json() + print(rj) + # store notifications - send them to task queue + tasks_queue.add_task((store_notifications_local, ([rj]))) + + +def mark_notification_read(api_key, notification_id): + ''' + mark notification as read + ''' + headers = utils.get_headers(api_key) + + url = paths.get_api_url() + f'notifications/mark-as-read/{notification_id}/' + params = {} + r = rerequests.get(url, params=params, verify=True, headers=headers) + if r is None: + return + # if r.status_code == 200: + # rj = r.json() + # # store notifications - send them to task queue + # print(rj) + # tasks_queue.add_task((mark_notification_read_local, ([notification_id]))) + diff --git a/blenderkit/icons.py b/blenderkit/icons.py index 09a1ba19..379062ed 100644 --- a/blenderkit/icons.py +++ b/blenderkit/icons.py @@ -33,6 +33,7 @@ icons_read = { 'royalty_free.png': 'royalty_free', 'filter.png': 'filter', 'filter_active.png': 'filter_active', + 'bell.png': 'bell', } verification_icons = { diff --git a/blenderkit/search.py b/blenderkit/search.py index 83729d1b..9c0e5ac2 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests, \ - resolutions, image_utils, ratings_utils + resolutions, image_utils, ratings_utils, comments_utils import blenderkit from bpy.app.handlers import persistent @@ -199,7 +199,6 @@ def fetch_server_data(): if bpy.context.window_manager.get('bkit_categories') is None: categories.fetch_categories_thread(api_key, force=False) - first_time = True last_clipboard = '' @@ -838,6 +837,9 @@ def get_profile(): a = bpy.context.window_manager.get('bkit profile') thread = threading.Thread(target=fetch_profile, args=(preferences.api_key,), daemon=True) thread.start() + if utils.experimental_enabled(): + comments_utils.get_notifications(preferences.api_key) + return a diff --git a/blenderkit/thumbnails/bell.png b/blenderkit/thumbnails/bell.png Binary files differnew file mode 100644 index 00000000..2b724a26 --- /dev/null +++ b/blenderkit/thumbnails/bell.png diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index af8f1993..f7dce680 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -592,6 +592,94 @@ class VIEW3D_PT_blenderkit_profile(Panel): icon='URL').url = paths.get_bkit_url() + paths.BLENDERKIT_USER_ASSETS +class MarkNotificationRead(bpy.types.Operator): + """Visit subcategory""" + bl_idname = "wm.blenderkit_mark_notification_read" + bl_label = "Mark notification as read" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + notification_id: bpy.props.IntProperty( + name="Id", + description="notification id", + default=-1) + + @classmethod + def poll(cls, context): + return True + + def execute(self, context): + notifications = bpy.context.window_manager['bkit notifications'] + for n in notifications: + if n['id'] == self.notification_id: + n['unread'] = 0 + comments_utils.check_notifications_read() + user_preferences = bpy.context.preferences.addons['blenderkit'].preferences + api_key = user_preferences.api_key + comments_utils.mark_notification_read(api_key,self.notification_id) + + return {'FINISHED'} + + +def draw_notification(self, notification, width = 600): + layout = self.layout + box = layout.box() + + firstline = f"user {notification['actor']['id']} {notification['verb']}" + box1 = box.box() + row = box1.row() + utils.label_multiline(row, text=firstline, width = width) + op = row.operator("wm.blenderkit_mark_notification_read", text="", icon='CHECKMARK') + op.notification_id = notification['id'] + utils.label_multiline(box, text=notification['description'], width = width) + +def draw_notifications(self, context, width = 600): + layout = self.layout + notifications = bpy.context.window_manager.get('bkit notifications') + if notifications is not None: + for notification in notifications: + if notification['unread'] == 1: + draw_notification(self,notification, width = width) + +class ShowNotifications(bpy.types.Operator): + """Show notifications""" + bl_idname = "wm.show_notifications" + bl_label = "Show BlenderKit notifications" + bl_options = {'REGISTER', 'UNDO'} + + notification_id: bpy.props.IntProperty( + name="Id", + description="notification id", + default=-1) + + @classmethod + def poll(cls, context): + return True + def draw(self, context): + draw_notifications(self,context, width = 600) + + def execute(self, context): + wm = bpy.context.window_manager + return wm.invoke_popup(self, width=600) + +class VIEW3D_PT_blenderkit_notifications(Panel): + bl_category = "BlenderKit" + bl_idname = "VIEW3D_PT_blenderkit_notifications" + bl_space_type = 'VIEW_3D' + bl_region_type = 'UI' + bl_label = "BlenderKit Notifications" + + @classmethod + def poll(cls, context): + notifications = bpy.context.window_manager.get('bkit notifications') + if notifications is not None and len(notifications) > 0: + return True + return False + def draw(self,context): + draw_notifications(self,context) + + + + class VIEW3D_PT_blenderkit_login(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_login" @@ -1944,27 +2032,25 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): else: role_text = "" box.label(text=f"{comment['submitDate']} - {comment['userName']}{role_text}") - utils.label_multiline(box, text=comment['comment'], width = width) + utils.label_multiline(box, text=comment['comment'], width=width) removal = False likes = 0 dislikes = 0 for l in comment['flags']: if l['flag'] == 'like': - likes +=1 + likes += 1 if l['flag'] == 'dislike': - dislikes +=1 + dislikes += 1 if l['flag'] == 'removal': removal = True row = box.row() - row.label(text = str(likes), icon = 'TRIA_UP') - row.label(text = str(dislikes), icon = 'TRIA_DOWN') + row.label(text=str(likes), icon='TRIA_UP') + row.label(text=str(dislikes), icon='TRIA_DOWN') if removal: - row.label(text = '', icon = 'ERROR') - - - #box.label(text=str(comment['flags'])) + row.label(text='', icon='ERROR') + # box.label(text=str(comment['flags'])) def draw(self, context): layout = self.layout @@ -1997,7 +2083,6 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): for comment in self.comments: self.draw_comment(context, layout, comment) - def execute(self, context): wm = context.window_manager ui_props = context.window_manager.blenderkitUI @@ -2031,10 +2116,9 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties): if utils.profile_is_validator() and bpy.app.debug_value == 2: user_preferences = bpy.context.preferences.addons['blenderkit'].preferences api_key = user_preferences.api_key - headers = utils.get_headers(api_key) comments = comments_utils.get_comments_local(asset_data['assetBaseId']) if comments is None: - comments_utils.get_comments(asset_data['assetBaseId'], headers) + comments_utils.get_comments(asset_data['assetBaseId'], api_key) comments = bpy.context.window_manager.get('asset comments', {}) self.comments = comments.get(asset_data['assetBaseId'], []) @@ -2332,9 +2416,15 @@ def header_search_draw(self, context): elif ui_props.asset_type == 'HDR': layout.popover(panel="VIEW3D_PT_blenderkit_advanced_HDR_search", text="", icon_value=icon_id) + notifications = bpy.context.window_manager.get('bkit notifications') + if notifications is not None and len(notifications) > 0: + layout.operator('wm.show_notifications', text="", icon_value=pcoll['bell'].icon_id) + # layout.popover(panel="VIEW3D_PT_blenderkit_notifications", text="", icon_value=pcoll['bell'].icon_id) + if utils.profile_is_validator(): search_props = utils.get_search_props() - layout.prop(search_props, 'search_verification_status', text ='') + layout.prop(search_props, 'search_verification_status', text='') + def ui_message(title, message): def draw_message(self, context): @@ -2352,6 +2442,7 @@ classes = ( SetCategoryOperator, VIEW3D_PT_blenderkit_profile, VIEW3D_PT_blenderkit_login, + VIEW3D_PT_blenderkit_notifications, VIEW3D_PT_blenderkit_unified, VIEW3D_PT_blenderkit_advanced_model_search, VIEW3D_PT_blenderkit_advanced_material_search, @@ -2369,6 +2460,8 @@ classes = ( UrlPopupDialog, ClosePopupButton, BlenderKitWelcomeOperator, + MarkNotificationRead, + ShowNotifications, ) |