From e670cee07525af65395a13f30f57ca5e656771a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vil=C3=A9m=20Duha?= <vilda.novak@gmail.com>
Date: Thu, 29 Apr 2021 12:13:40 +0200
Subject: [PATCH] BlenderKit: improve right click menu

-basically a complete revamp of the code, enables to display asset information in a much cleaner way with more tooltips e.t.c.
-simplified floating asset preview - name needs to be fixed still, and tooltip generation cleaned
-added several new icons, deleted one unused
-improve a lot of tooltips
-fix rerender of thumbnails in unsaved files (would save assets into addon directory)
-reorganize some rating functions into ratings_utils.py
-new 'wrap' operator for web links, allows to have custom tooltips for each link
---
 blenderkit/__init__.py                 |  10 +-
 blenderkit/asset_bar_op.py             |  11 +-
 blenderkit/autothumb.py                |  15 +-
 blenderkit/autothumb_material_bg.py    |   6 +-
 blenderkit/autothumb_model_bg.py       |   8 +-
 blenderkit/icons.py                    |  16 +-
 blenderkit/ratings.py                  | 217 +++--------
 blenderkit/ratings_utils.py            | 121 +++++++
 blenderkit/search.py                   | 212 +++++++++--
 blenderkit/thumbnails/cc0.png          | Bin 0 -> 5419 bytes
 blenderkit/thumbnails/locked_large.png | Bin 3170 -> 0 bytes
 blenderkit/thumbnails/private.png      | Bin 0 -> 540 bytes
 blenderkit/thumbnails/royalty_free.png | Bin 0 -> 4755 bytes
 blenderkit/thumbnails/trophy.png       | Bin 0 -> 3078 bytes
 blenderkit/thumbnails/vs_validated.png | Bin 0 -> 2393 bytes
 blenderkit/ui.py                       |  32 +-
 blenderkit/ui_panels.py                | 483 +++++++++++++++++++++----
 blenderkit/upload.py                   |   7 +-
 blenderkit/utils.py                    |  13 +
 19 files changed, 859 insertions(+), 292 deletions(-)
 create mode 100644 blenderkit/ratings_utils.py
 create mode 100644 blenderkit/thumbnails/cc0.png
 delete mode 100644 blenderkit/thumbnails/locked_large.png
 create mode 100644 blenderkit/thumbnails/private.png
 create mode 100644 blenderkit/thumbnails/royalty_free.png
 create mode 100644 blenderkit/thumbnails/trophy.png
 create mode 100644 blenderkit/thumbnails/vs_validated.png

diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
index 9633ae507..02d17f0bc 100644
--- a/blenderkit/__init__.py
+++ b/blenderkit/__init__.py
@@ -49,6 +49,7 @@ if "bpy" in locals():
     overrides = reload(overrides)
     paths = reload(paths)
     ratings = reload(ratings)
+    ratings_utils = reload(ratings_utils)
     resolutions = reload(resolutions)
     search = reload(search)
     tasks_queue = reload(tasks_queue)
@@ -84,6 +85,7 @@ else:
     from blenderkit import overrides
     from blenderkit import paths
     from blenderkit import ratings
+    from blenderkit import ratings_utils
     from blenderkit import resolutions
     from blenderkit import search
     from blenderkit import tasks_queue
@@ -731,20 +733,20 @@ class BlenderKitRatingProps(PropertyGroup):
                                 description="quality of the material",
                                 default=0,
                                 min=-1, max=10,
-                                update=ratings.update_ratings_quality)
+                                update=ratings_utils.update_ratings_quality)
 
     # the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
     rating_quality_ui: EnumProperty(name='rating_quality_ui',
-                                    items=ratings.stars_enum_callback,
+                                    items=ratings_utils.stars_enum_callback,
                                     description='Rating stars 0 - 10',
                                     default=None,
-                                    update=ratings.update_quality_ui,
+                                    update=ratings_utils.update_quality_ui,
                                     )
 
     rating_work_hours: FloatProperty(name="Work Hours",
                                      description="How many hours did this work take?",
                                      default=0.00,
-                                     min=0.0, max=150, update=ratings.update_ratings_work_hours
+                                     min=0.0, max=150, update=ratings_utils.update_ratings_work_hours
                                      )
 
     # rating_complexity: IntProperty(name="Complexity",
diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py
index acb9cf943..30d94e27a 100644
--- a/blenderkit/asset_bar_op.py
+++ b/blenderkit/asset_bar_op.py
@@ -225,9 +225,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
         self.asset_name = name_label
         self.tooltip_widgets.append(name_label)
         offset_y = 16 + self.margin
-        label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y,
-                              text_size=14)
-        self.tooltip_widgets.append(label)
+        # label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y,
+        #                       text_size=14)
+        # self.tooltip_widgets.append(label)
 
 
         self.hide_tooltip()
@@ -505,6 +505,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
     # handlers
 
     def enter_button(self, widget):
+        # context.window.cursor_warp(event.mouse_x, event.mouse_y - 20);
+
+
         self.show_tooltip()
 
         if self.active_index != widget.search_index:
@@ -532,6 +535,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
 
             self.tooltip_panel.update(tooltip_x, widget.y_screen + widget.height)
             self.tooltip_panel.layout_widgets()
+            # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
+
 
     def exit_button(self, widget):
         # this condition checks if there wasn't another button already entered, which can happen with small button gaps
diff --git a/blenderkit/autothumb.py b/blenderkit/autothumb.py
index 26697d740..baf1a2caa 100644
--- a/blenderkit/autothumb.py
+++ b/blenderkit/autothumb.py
@@ -156,7 +156,7 @@ def start_thumbnailer(self=None, json_args=None, props=None, wait=False, add_bg_
         eval_path_state = "bpy.data.objects['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
         eval_path = "bpy.data.objects['%s']" % json_args['asset_name']
 
-        bg_blender.add_bg_process(eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
+        bg_blender.add_bg_process(name = f"{json_args['asset_name']} thumbnailer" ,eval_path_computing=eval_path_computing, eval_path_state=eval_path_state,
                                   eval_path=eval_path, process_type='THUMBNAILER', process=proc)
 
 
@@ -206,7 +206,7 @@ def start_material_thumbnailer(self=None, json_args=None, props=None, wait=False
         eval_path_state = "bpy.data.materials['%s'].blenderkit.thumbnail_generating_state" % json_args['asset_name']
         eval_path = "bpy.data.materials['%s']" % json_args['asset_name']
 
-        bg_blender.add_bg_process(name=json_args['asset_name'], eval_path_computing=eval_path_computing,
+        bg_blender.add_bg_process(name=f"{json_args['asset_name']} thumbnailer", eval_path_computing=eval_path_computing,
                                   eval_path_state=eval_path_state,
                                   eval_path=eval_path, process_type='THUMBNAILER', process=proc)
         if props:
@@ -328,7 +328,10 @@ class GenerateThumbnailOperator(bpy.types.Operator):
 
 
 class ReGenerateThumbnailOperator(bpy.types.Operator):
-    """Generate Cycles thumbnail for model assets"""
+    """
+        Generate default thumbnail with Cycles renderer and upload it.
+        Works also for assets from search results, without being downloaded before.
+    """
     bl_idname = "object.blenderkit_regenerate_thumbnail"
     bl_label = "BlenderKit Thumbnail Re-generate"
     bl_options = {'REGISTER', 'INTERNAL'}
@@ -371,11 +374,9 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
         return True  # bpy.context.view_layer.objects.active is not None
 
     def draw(self, context):
-        ob = bpy.context.active_object
-        while ob.parent is not None:
-            ob = ob.parent
         props = self
         layout = self.layout
+        # layout.label('This will re-generate thumbnail and directly upload it to server. You should see your updated thumbnail online depending ')
         layout.label(text='thumbnailer settings')
         layout.prop(props, 'thumbnail_background_lightness')
         layout.prop(props, 'thumbnail_angle')
@@ -521,7 +522,7 @@ class GenerateMaterialThumbnailOperator(bpy.types.Operator):
 
 class ReGenerateMaterialThumbnailOperator(bpy.types.Operator):
     """
-        Generate default thumbnail with Cycles renderer.
+        Generate default thumbnail with Cycles renderer and upload it.
         Works also for assets from search results, without being downloaded before.
     """
     bl_idname = "object.blenderkit_regenerate_material_thumbnail"
diff --git a/blenderkit/autothumb_material_bg.py b/blenderkit/autothumb_material_bg.py
index 0a0ce5dbd..3ae0d5dd2 100644
--- a/blenderkit/autothumb_material_bg.py
+++ b/blenderkit/autothumb_material_bg.py
@@ -20,7 +20,7 @@
 
 from blenderkit import utils, append_link, bg_blender, upload_bg, download
 
-import sys, json, math
+import sys, json, math, os
 import bpy
 from pathlib import Path
 
@@ -48,6 +48,10 @@ if __name__ == "__main__":
             data = json.load(s)
             # append_material(file_name, matname = None, link = False, fake_user = True)
         if data.get('do_download'):
+            #need to save the file, so that asset doesn't get downloaded into addon directory
+            temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
+            bpy.ops.wm.save_as_mainfile(filepath=temp_blend_path)
+
             asset_data = data['asset_data']
             has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None,
                                                 resolution='blend')
diff --git a/blenderkit/autothumb_model_bg.py b/blenderkit/autothumb_model_bg.py
index 9be56d9f7..d8e529fd6 100644
--- a/blenderkit/autothumb_model_bg.py
+++ b/blenderkit/autothumb_model_bg.py
@@ -20,8 +20,7 @@
 
 from blenderkit import utils, append_link, bg_blender, download, upload_bg
 
-import sys, json, math
-from pathlib import Path
+import sys, json, math, os
 import bpy
 import mathutils
 
@@ -86,8 +85,11 @@ if __name__ == "__main__":
 
 
         if data.get('do_download'):
-            bg_blender.progress('Downloading asset')
+            #need to save the file, so that asset doesn't get downloaded into addon directory
+            temp_blend_path = os.path.join(data['tempdir'], 'temp.blend')
+            bpy.ops.wm.save_as_mainfile(filepath = temp_blend_path)
 
+            bg_blender.progress('Downloading asset')
             asset_data = data['asset_data']
             has_url = download.get_download_url(asset_data, download.get_scene_id(), user_preferences.api_key, tcom=None,
                                                 resolution='blend')
diff --git a/blenderkit/icons.py b/blenderkit/icons.py
index 45c729df3..ab17efcfe 100644
--- a/blenderkit/icons.py
+++ b/blenderkit/icons.py
@@ -27,9 +27,23 @@ icon_collections = {}
 icons_read = {
     'fp.png': 'free',
     'flp.png': 'full',
-    'test.jpg': 'test',
+    'trophy.png': 'trophy',
+    'cc0.png': 'cc0',
+    'royalty_free.png': 'royalty_free',
 }
 
+verification_icons = {
+    'vs_ready.png':'ready',
+    'vs_deleted.png':'deleted' ,
+    'vs_uploaded.png': 'uploaded',
+    'vs_uploading.png': 'uploading',
+    'vs_on_hold.png': 'on_hold',
+    'vs_validated.png': 'validated',
+    'vs_rejected.png': 'rejected'
+
+}
+
+icons_read.update(verification_icons)
 
 def register_icons():
     # Note that preview collections returned by bpy.utils.previews
diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py
index c70559053..118cc3aec 100644
--- a/blenderkit/ratings.py
+++ b/blenderkit/ratings.py
@@ -16,7 +16,7 @@
 #
 # ##### END GPL LICENSE BLOCK #####
 
-from blenderkit import paths, utils, rerequests, tasks_queue
+from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils
 
 import bpy
 import requests, threading
@@ -103,42 +103,6 @@ def get_rating(asset_id):
         print(r.text)
 
 
-def update_ratings_quality(self, context):
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    api_key = user_preferences.api_key
-
-    headers = utils.get_headers(api_key)
-    asset = self.id_data
-    if asset:
-        bkit_ratings = asset.bkit_ratings
-        url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
-    else:
-        # this part is for operator rating:
-        bkit_ratings = self
-        url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
-
-    if bkit_ratings.rating_quality > 0.1:
-        ratings = [('quality', bkit_ratings.rating_quality)]
-        tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
-
-
-def update_ratings_work_hours(self, context):
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    api_key = user_preferences.api_key
-    headers = utils.get_headers(api_key)
-    asset = self.id_data
-    if asset:
-        bkit_ratings = asset.bkit_ratings
-        url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
-    else:
-        # this part is for operator rating:
-        bkit_ratings = self
-        url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
-
-    if bkit_ratings.rating_work_hours > 0.45:
-        ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
-        tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
-
 
 def upload_rating(asset):
     user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@@ -242,69 +206,40 @@ class UploadRatingOperator(bpy.types.Operator):
         return wm.invoke_props_dialog(self)
 
 
-def stars_enum_callback(self, context):
-    '''regenerates the enum property used to display rating stars, so that there are filled/empty stars correctly.'''
-    items = []
-    for a in range(0, 10):
-        if self.rating_quality < a + 1:
-            icon = 'SOLO_OFF'
-        else:
-            icon = 'SOLO_ON'
-        # has to have something before the number in the value, otherwise fails on registration.
-        items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
-    return items
-
-
-def update_quality_ui(self, context):
-    '''Converts the _ui the enum into actual quality number.'''
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    if user_preferences.api_key == '':
-        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
-        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
-        # return
-        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
-                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
-        # self.rating_quality_ui = '0'
-    self.rating_quality = int(self.rating_quality_ui)
-
 
-def update_ratings_work_hours_ui(self, context):
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    if user_preferences.api_key == '':
-        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
-        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
-        # return
-        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
-                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
-        # self.rating_work_hours_ui = '0'
-    self.rating_work_hours = float(self.rating_work_hours_ui)
+def draw_ratings_menu(self, context, layout):
+    col = layout.column()
+    # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
+    row = col.row()
+    row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
+    # row.label(text=str(self.rating_quality))
+    col.separator()
 
+    row = layout.row()
+    row.label(text=f"How many hours did this {self.asset_type} save you?")
 
-def update_ratings_work_hours_ui_1_5(self, context):
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    if user_preferences.api_key == '':
-        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
-        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
-        # return
-        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
-                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
-        # self.rating_work_hours_ui_1_5 = '0'
-    # print('updating 1-5')
-    # print(float(self.rating_work_hours_ui_1_5))
-    self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
-
-def update_ratings_work_hours_ui_1_10(self, context):
-    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
-    if user_preferences.api_key == '':
-        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
-        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
-        # return
-        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
-                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
-        # self.rating_work_hours_ui_1_5 = '0'
-    # print('updating 1-5')
-    # print(float(self.rating_work_hours_ui_1_5))
-    self.rating_work_hours = float(self.rating_work_hours_ui_1_10)
+    if self.asset_type in ('model', 'scene'):
+        row = layout.row()
+        if utils.profile_is_validator():
+            col.prop(self, 'rating_work_hours')
+        row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
+        if float(self.rating_work_hours_ui) > 100:
+            utils.label_multiline(layout,
+                                  text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
+                                  width=500)
+        elif float(self.rating_work_hours_ui) > 18:
+            layout.separator()
+
+            utils.label_multiline(layout,
+                                  text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
+                                  width=500)
+
+    elif self.asset_type == 'hdr':
+        row = layout.row()
+        row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
+    else:
+        row = layout.row()
+        row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
 
 
 class FastRateMenu(Operator):
@@ -341,22 +276,22 @@ class FastRateMenu(Operator):
                                 description="quality of the material",
                                 default=0,
                                 min=-1, max=10,
-                                # update=update_ratings_quality,
+                                # update=ratings_utils.update_ratings_quality,
                                 options={'SKIP_SAVE'})
 
     # the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
     rating_quality_ui: EnumProperty(name='rating_quality_ui',
-                                    items=stars_enum_callback,
+                                    items=ratings_utils.stars_enum_callback,
                                     description='Rating stars 0 - 10',
                                     default=0,
-                                    update=update_quality_ui,
+                                    update=ratings_utils.update_quality_ui,
                                     options={'SKIP_SAVE'})
 
     rating_work_hours: FloatProperty(name="Work Hours",
                                      description="How many hours did this work take?",
                                      default=0.00,
                                      min=0.0, max=300,
-                                     # update=update_ratings_work_hours,
+                                     # update=ratings_utils.update_ratings_work_hours,
                                      options={'SKIP_SAVE'}
                                      )
 
@@ -383,8 +318,8 @@ class FastRateMenu(Operator):
                                               ('200', '200', high_rating_warning),
                                               ('250', '250', high_rating_warning),
                                               ],
-                                       default='0', update=update_ratings_work_hours_ui,
-                                       options = {'SKIP_SAVE'}
+                                       default='0', update=ratings_utils.update_ratings_work_hours_ui,
+                                       options={'SKIP_SAVE'}
                                        )
 
     rating_work_hours_ui_1_5: EnumProperty(name="Work Hours",
@@ -399,28 +334,28 @@ class FastRateMenu(Operator):
                                                   ('5', '5', '')
                                                   ],
                                            default='0',
-                                           update=update_ratings_work_hours_ui_1_5,
-                                           options = {'SKIP_SAVE'}
+                                           update=ratings_utils.update_ratings_work_hours_ui_1_5,
+                                           options={'SKIP_SAVE'}
                                            )
 
     rating_work_hours_ui_1_10: EnumProperty(name="Work Hours",
-                                           description="How many hours did this work take?",
-                                           items=[('0', '0', ''),
-                                                  ('1', '1', ''),
-                                                  ('2', '2', ''),
-                                                  ('3', '3', ''),
-                                                  ('4', '4', ''),
-                                                  ('5', '5', ''),
-                                                  ('6', '6', ''),
-                                                  ('7', '7', ''),
-                                                  ('8', '8', ''),
-                                                  ('9', '9', ''),
-                                                  ('10', '10', '')
-                                                  ],
-                                           default='0',
-                                           update=update_ratings_work_hours_ui_1_10,
-                                           options={'SKIP_SAVE'}
-                                           )
+                                            description="How many hours did this work take?",
+                                            items=[('0', '0', ''),
+                                                   ('1', '1', ''),
+                                                   ('2', '2', ''),
+                                                   ('3', '3', ''),
+                                                   ('4', '4', ''),
+                                                   ('5', '5', ''),
+                                                   ('6', '6', ''),
+                                                   ('7', '7', ''),
+                                                   ('8', '8', ''),
+                                                   ('9', '9', ''),
+                                                   ('10', '10', '')
+                                                   ],
+                                            default='0',
+                                            update=ratings_utils.update_ratings_work_hours_ui_1_10,
+                                            options={'SKIP_SAVE'}
+                                            )
 
     @classmethod
     def poll(cls, context):
@@ -430,41 +365,9 @@ class FastRateMenu(Operator):
 
     def draw(self, context):
         layout = self.layout
-        col = layout.column()
-
-        # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
-        col.label(text=self.message)
-        row = col.row()
-        row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
-        # row.label(text=str(self.rating_quality))
-        col.separator()
-
-        row = layout.row()
-        row.label(text=f"How many hours did this {self.asset_type} save you?")
-
-        if self.asset_type in ('model', 'scene'):
-            row = layout.row()
-            if utils.profile_is_validator():
-                col.prop(self, 'rating_work_hours')
-            row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True)
-            if float(self.rating_work_hours_ui) > 100:
-                utils.label_multiline(layout,
-                                      text=f"\nThat's huge! please be sure to give such rating only to godly {self.asset_type}s.\n",
-                                      width=500)
-            elif float(self.rating_work_hours_ui) > 18:
-                layout.separator()
-
-                utils.label_multiline(layout,
-                                      text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
-                                      width=500)
-
-        elif self.asset_type == 'hdr':
-            row = layout.row()
-            row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
-        else:
-            row = layout.row()
-            row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
+        layout.label(text=self.message)
 
+        draw_ratings_menu(self, context, layout)
 
     def execute(self, context):
         user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
@@ -505,7 +408,7 @@ class FastRateMenu(Operator):
         self.message = f"Rate asset {self.asset_name}"
         wm = context.window_manager
 
-        if self.asset_type in ('model','scene'):
+        if self.asset_type in ('model', 'scene'):
             # spawn a wider one for validators for the enum buttons
             return wm.invoke_props_dialog(self, width=500)
         else:
diff --git a/blenderkit/ratings_utils.py b/blenderkit/ratings_utils.py
new file mode 100644
index 000000000..9dc02e550
--- /dev/null
+++ b/blenderkit/ratings_utils.py
@@ -0,0 +1,121 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+#mainly update functions and callbacks for ratings properties, here to avoid circular imports.
+import bpy
+
+def update_ratings_quality(self, context):
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    api_key = user_preferences.api_key
+
+    headers = utils.get_headers(api_key)
+    asset = self.id_data
+    if asset:
+        bkit_ratings = asset.bkit_ratings
+        url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
+    else:
+        # this part is for operator rating:
+        bkit_ratings = self
+        url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
+
+    if bkit_ratings.rating_quality > 0.1:
+        ratings = [('quality', bkit_ratings.rating_quality)]
+        tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
+
+
+def update_ratings_work_hours(self, context):
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    api_key = user_preferences.api_key
+    headers = utils.get_headers(api_key)
+    asset = self.id_data
+    if asset:
+        bkit_ratings = asset.bkit_ratings
+        url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
+    else:
+        # this part is for operator rating:
+        bkit_ratings = self
+        url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
+
+    if bkit_ratings.rating_work_hours > 0.45:
+        ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
+        tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True)
+
+def update_quality_ui(self, context):
+    '''Converts the _ui the enum into actual quality number.'''
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    if user_preferences.api_key == '':
+        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
+        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
+        # return
+        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
+                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
+        # self.rating_quality_ui = '0'
+    self.rating_quality = int(self.rating_quality_ui)
+
+
+def update_ratings_work_hours_ui(self, context):
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    if user_preferences.api_key == '':
+        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
+        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
+        # return
+        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
+                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
+        # self.rating_work_hours_ui = '0'
+    self.rating_work_hours = float(self.rating_work_hours_ui)
+
+
+def update_ratings_work_hours_ui_1_5(self, context):
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    if user_preferences.api_key == '':
+        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
+        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
+        # return
+        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
+                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
+        # self.rating_work_hours_ui_1_5 = '0'
+    # print('updating 1-5')
+    # print(float(self.rating_work_hours_ui_1_5))
+    self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
+
+
+def update_ratings_work_hours_ui_1_10(self, context):
+    user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
+    if user_preferences.api_key == '':
+        # ui_panels.draw_not_logged_in(self, message='Please login/signup to rate assets.')
+        # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_login_menu')
+        # return
+        bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
+                                    message='Please login/signup to rate assets. Clicking OK takes you to web login.')
+        # self.rating_work_hours_ui_1_5 = '0'
+    # print('updating 1-5')
+    # print(float(self.rating_work_hours_ui_1_5))
+    self.rating_work_hours = float(self.rating_work_hours_ui_1_10)
+
+
+def stars_enum_callback(self, context):
+    '''regenerates the enum property used to display rating stars, so that there are filled/empty stars correctly.'''
+    items = []
+    for a in range(0, 10):
+        if self.rating_quality < a + 1:
+            icon = 'SOLO_OFF'
+        else:
+            icon = 'SOLO_ON'
+        # has to have something before the number in the value, otherwise fails on registration.
+        items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
+    return items
\ No newline at end of file
diff --git a/blenderkit/search.py b/blenderkit/search.py
index 1465d1038..3f0532b5f 100644
--- a/blenderkit/search.py
+++ b/blenderkit/search.py
@@ -572,10 +572,6 @@ def writeblockm(tooltip, mdata, key='', pretext=None, width=40):  # for longer t
     return tooltip
 
 
-def fmt_length(prop):
-    prop = str(round(prop, 2))
-    return prop
-
 
 def has(mdata, prop):
     if mdata.get(prop) is not None and mdata[prop] is not None and mdata[prop] is not False:
@@ -583,8 +579,152 @@ def has(mdata, prop):
     else:
         return False
 
-
 def generate_tooltip(mdata):
+    col_w = 40
+    if type(mdata['parameters']) == list:
+        mparams = utils.params_to_dict(mdata['parameters'])
+    else:
+        mparams = mdata['parameters']
+    t = ''
+    # t = writeblock(t, mdata['displayName'], width=col_w)
+    # t += '\n'
+
+    t = writeblockm(t, mdata, key='description', pretext='', width=col_w)
+    if mdata['description'] != '':
+        t += '\n'
+
+    bools = (('rig', None), ('animated', None), ('manifold', 'non-manifold'), ('scene', None), ('simulation', None),
+             ('uv', None))
+    for b in bools:
+        if mparams.get(b[0]):
+            mdata['tags'].append(b[0])
+        elif b[1] != None:
+            mdata['tags'].append(b[1])
+
+    bools_data = ('adult',)
+    for b in bools_data:
+        if mdata.get(b) and mdata[b]:
+            mdata['tags'].append(b)
+    t = writeblockm(t, mparams, key='designer', pretext='Designer', width=col_w)
+    t = writeblockm(t, mparams, key='manufacturer', pretext='Manufacturer', width=col_w)
+    t = writeblockm(t, mparams, key='designCollection', pretext='Design collection', width=col_w)
+
+    # t = writeblockm(t, mparams, key='engines', pretext='engine', width = col_w)
+    # t = writeblockm(t, mparams, key='model_style', pretext='style', width = col_w)
+    # t = writeblockm(t, mparams, key='material_style', pretext='style', width = col_w)
+    # t = writeblockm(t, mdata, key='tags', width = col_w)
+    # t = writeblockm(t, mparams, key='condition', pretext='condition', width = col_w)
+    # t = writeblockm(t, mparams, key='productionLevel', pretext='production level', width = col_w)
+    if has(mdata, 'purePbr'):
+        t = writeblockm(t, mparams, key='pbrType', pretext='Pbr', width=col_w)
+
+    t = writeblockm(t, mparams, key='designYear', pretext='Design year', width=col_w)
+
+    if has(mparams, 'dimensionX'):
+        t += 'Size: %s × %s × %s m\n' % (utils.fmt_length(mparams['dimensionX']),
+                                     utils.fmt_length(mparams['dimensionY']),
+                                     utils.fmt_length(mparams['dimensionZ']))
+    if has(mparams, 'faceCount') and mdata['assetType'] == 'model':
+        t += 'Face count: %s\n' % (mparams['faceCount'])
+        # t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender'])
+
+    # write files size - this doesn't reflect true file size, since files size is computed from all asset files, including resolutions.
+    # if mdata.get('filesSize'):
+    #     fs = utils.files_size_to_text(mdata['filesSize'])
+    #     t += f'files size: {fs}\n'
+
+    # t = writeblockm(t, mparams, key='meshPolyType', pretext='mesh type', width = col_w)
+    # t = writeblockm(t, mparams, key='objectCount', pretext='nubmber of objects', width = col_w)
+
+    # t = writeblockm(t, mparams, key='materials', width = col_w)
+    # t = writeblockm(t, mparams, key='modifiers', width = col_w)
+    # t = writeblockm(t, mparams, key='shaders', width = col_w)
+
+    # if has(mparams, 'textureSizeMeters'):
+    #     t += 'Texture size: %s m\n' % utils.fmt_length(mparams['textureSizeMeters'])
+
+    if has(mparams, 'textureResolutionMax') and mparams['textureResolutionMax'] > 0:
+        if not mparams.get('textureResolutionMin'):  # for HDR's
+            t = writeblockm(t, mparams, key='textureResolutionMax', pretext='Resolution', width=col_w)
+        elif mparams.get('textureResolutionMin') == mparams['textureResolutionMax']:
+            t = writeblockm(t, mparams, key='textureResolutionMin', pretext='Texture resolution', width=col_w)
+        else:
+            t += 'Tex resolution: %i - %i\n' % (mparams.get('textureResolutionMin'), mparams['textureResolutionMax'])
+
+    if has(mparams, 'thumbnailScale'):
+        t = writeblockm(t, mparams, key='thumbnailScale', pretext='Preview scale', width=col_w)
+
+    # t += 'uv: %s\n' % mdata['uv']
+    # t += '\n'
+    if mdata.get('license') == 'cc_zero':
+        t+= 'license: CC Zero\n'
+    else:
+        t+= 'license: Royalty free\n'
+    # t = writeblockm(t, mdata, key='license', width=col_w)
+
+    fs = mdata.get('files')
+
+    if utils.profile_is_validator():
+        if fs and len(fs) > 2:
+            resolutions = 'Resolutions:'
+            list.sort(fs, key=lambda f: f['fileType'])
+            for f in fs:
+                if f['fileType'].find('resolution') > -1:
+                    resolutions += f['fileType'][11:] + ' '
+            resolutions += '\n'
+            t += resolutions.replace('_', '.')
+
+        # if mdata['isFree']:
+        #     t += 'Free plan\n'
+        # else:
+        #     t += 'Full plan\n'
+    else:
+        if fs:
+            for f in fs:
+                if f['fileType'].find('resolution') > -1:
+                    t += 'Asset has lower resolutions available\n'
+                    break;
+
+    # 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', width = col_w)
+    #     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)
+
+
+    # t += '\n'
+    # rc = mdata.get('ratingsCount')
+    # if rc:
+    #     t+='\n'
+    #     if rc:
+    #         rcount = min(rc['quality'], rc['workingHours'])
+    #     else:
+    #         rcount = 0
+    #
+    #     show_rating_threshold = 5
+    #
+    #     if rcount < show_rating_threshold and mdata['assetType'] != 'hdr':
+    #         t += f"Only assets with enough ratings \nshow the rating value. Please rate.\n"
+    #     if rc['quality'] >= show_rating_threshold:
+    #         # t += f"{int(mdata['ratingsAverage']['quality']) * '*'}\n"
+    #         t += f"* {round(mdata['ratingsAverage']['quality'],1)}\n"
+    #     if rc['workingHours'] >= show_rating_threshold:
+    #         t += f"Hours saved: {int(mdata['ratingsAverage']['workingHours'])}\n"
+    #     if utils.profile_is_validator():
+    #         t += f"Score: {int(mdata['score'])}\n"
+    #
+    #         t += f"Ratings count {rc['quality']}*/{rc['workingHours']}wh value " \
+    #              f"{(mdata['ratingsAverage']['quality'],1)}*/{(mdata['ratingsAverage']['workingHours'],1)}wh\n"
+    # if len(t.split('\n')) < 11:
+    #     t += '\n'
+    #     t += get_random_tip(mdata)
+    #     t += '\n'
+    return t
+
+def generate_tooltip_old(mdata):
     col_w = 40
     if type(mdata['parameters']) == list:
         mparams = utils.params_to_dict(mdata['parameters'])
@@ -626,9 +766,9 @@ def generate_tooltip(mdata):
     t = writeblockm(t, mparams, key='designYear', pretext='Design year', width=col_w)
 
     if has(mparams, 'dimensionX'):
-        t += 'Size: %s x %s x %sm\n' % (fmt_length(mparams['dimensionX']),
-                                     fmt_length(mparams['dimensionY']),
-                                     fmt_length(mparams['dimensionZ']))
+        t += 'Size: %s x %s x %sm\n' % (utils.fmt_length(mparams['dimensionX']),
+                                     utils.fmt_length(mparams['dimensionY']),
+                                     utils.fmt_length(mparams['dimensionZ']))
     if has(mparams, 'faceCount') and mdata['assetType'] == 'model':
         t += 'Face count: %s\n' % (mparams['faceCount'])
         # t += 'face count: %s, render: %s\n' % (mparams['faceCount'], mparams['faceCountRender'])
@@ -646,7 +786,7 @@ def generate_tooltip(mdata):
     # t = writeblockm(t, mparams, key='shaders', width = col_w)
 
     # if has(mparams, 'textureSizeMeters'):
-    #     t += 'Texture size: %s m\n' % fmt_length(mparams['textureSizeMeters'])
+    #     t += 'Texture size: %s m\n' % utils.fmt_length(mparams['textureSizeMeters'])
 
     if has(mparams, 'textureResolutionMax') and mparams['textureResolutionMax'] > 0:
         if not mparams.get('textureResolutionMin'):  # for HDR's
@@ -729,37 +869,24 @@ def generate_tooltip(mdata):
     return t
 
 
-def get_random_tip(mdata):
+def get_random_tip():
     t = ''
-
     tip = 'Tip: ' + random.choice(rtips)
     t = writeblock(t, tip)
     return t
-    # at = mdata['assetType']
-    # if at == 'brush' or at == 'texture':
-    #     t += 'click to link %s' % mdata['assetType']
-    # if at == 'model' or at == 'material':
-    #     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)
-    return t
 
 
 def generate_author_textblock(adata):
-    t = '\n\n\n'
+    t = ''
 
     if adata not in (None, ''):
-        col_w = 40
+        col_w = 2000
         if len(adata['firstName'] + adata['lastName']) > 0:
-            t = 'Author:\n'
-            t += '%s %s\n' % (adata['firstName'], adata['lastName'])
+            t = 'Author: %s %s\n' % (adata['firstName'], adata['lastName'])
             t += '\n'
-            if adata.get('aboutMeUrl') is not None:
-                t = writeblockm(t, adata, key='aboutMeUrl', pretext='', width=col_w)
-                t += '\n'
+            # if adata.get('aboutMeUrl') is not None:
+            #     t = writeblockm(t, adata, key='aboutMeUrl', pretext='', width=col_w)
+            #     t += '\n'
             if adata.get('aboutMe') is not None:
                 t = writeblockm(t, adata, key='aboutMe', pretext='', width=col_w)
                 t += '\n'
@@ -1528,6 +1655,7 @@ class SearchOperator(Operator):
     bl_label = "BlenderKit asset search"
     bl_description = "Search online for assets"
     bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+
     own: BoolProperty(name="own assets only",
                       description="Find all own assets",
                       default=False)
@@ -1559,6 +1687,12 @@ class SearchOperator(Operator):
         options={'SKIP_SAVE'}
     )
 
+    tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
+
+    @classmethod
+    def description(cls, context, properties):
+        return properties.tooltip
+
     @classmethod
     def poll(cls, context):
         return True
@@ -1577,9 +1711,27 @@ class SearchOperator(Operator):
 
         return {'FINISHED'}
 
+class UrlOperator(Operator):
+    """"""
+    bl_idname = "wm.blenderkit_url"
+    bl_label = ""
+    bl_description = "Search online for assets"
+    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+
+    tooltip: bpy.props.StringProperty(default='Open a web page')
+    url: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
+
+    @classmethod
+    def description(cls, context, properties):
+        return properties.tooltip
+
+    def execute(self,context):
+        bpy.ops.wm.url_open(url=self.url)
+
 
 classes = [
-    SearchOperator
+    SearchOperator,
+    UrlOperator
 ]
 
 
diff --git a/blenderkit/thumbnails/cc0.png b/blenderkit/thumbnails/cc0.png
new file mode 100644
index 0000000000000000000000000000000000000000..3f5450f84503b5813c909c761e91a0b8ed880310
GIT binary patch
literal 5419
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
PU|{fc^>bP0l+XkKA!y(d

literal 0
HcmV?d00001

diff --git a/blenderkit/thumbnails/locked_large.png b/blenderkit/thumbnails/locked_large.png
deleted file mode 100644
index eb2deb65e3da8ac16699d82d250fed3ef0f5bbd3..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 3170
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
X+k4+q%e%|Kz`)??>gTe~DWM4fn26+U

diff --git a/blenderkit/thumbnails/private.png b/blenderkit/thumbnails/private.png
new file mode 100644
index 0000000000000000000000000000000000000000..7ac3c3d75ad7181f0105f40a4cc9001ae12b8ce8
GIT binary patch
literal 540
zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F<Zz`I*kn0h!6k3=A3*ODFnz
zGX;vY$#2y-Ww1hF(NP_pC=CzCEj-P4BDhUYWG#Li{@}R5;+8GEx3&mGx#a$e`of<j
zy+xO!NvR{_qW+6)VWYW$j-0lK=YIZh@3Y)HTk$ss*UU2BRWsqV_}72~>({tlpE9pJ
zr^ld!Io`)~%hC@?Eqxz9Yg`rLk6Ru7eAl*%^F5@rZdA3Vhx+Pso#$rX-ZkrJ^`XOA
zYSRSl1i!r~a(Ec+-GBY(*+(UF_v>6!UdXy3+IDIQ_wne6vo9<kY9^NJMF&Yg-<bEv
z=UMonNyk%qlLeF-<XbIN%}xd#(YDk#&VK*-YRzepBa2VPFYeLnGm-pQ<)Z6(eaVw4
zFFb#4nzDB1|M|-&&YHQdy!_X~-&alS)fc48uM)ld$JX@hD@lvE2UhpLh^P1!-(M6y
zYu0ax80&({%U2$;7i{HTd_Kyj|Efy6PPG=Rytsit>%!_6(d*Bz+Iaq_u*<fmo$nre
z;80t7Z|g_)r_SmBZcfgb$-uzCS>O>_%)r1c48n{Iv*t)JFfasrx;Tbp+<SY`ke5M$
z$JMb*O+}I0v!bbYgR$wI+q?dHdrzF#_4`HcTAszndp7S*`_F(69EfLlxsi8SAxntx
RTUBO|N>5immvv4FO#stt;C=uA

literal 0
HcmV?d00001

diff --git a/blenderkit/thumbnails/royalty_free.png b/blenderkit/thumbnails/royalty_free.png
new file mode 100644
index 0000000000000000000000000000000000000000..b85fa91096c6cb970372f40e5b837709197d2c05
GIT binary patch
literal 4755
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

literal 0
HcmV?d00001

diff --git a/blenderkit/thumbnails/trophy.png b/blenderkit/thumbnails/trophy.png
new file mode 100644
index 0000000000000000000000000000000000000000..01d77a226ee0cce9deb5f167a70349ad40147751
GIT binary patch
literal 3078
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
Zhr-tmJWLh%%fP_E;OXk;vd$@?2>`_~e8T_$

literal 0
HcmV?d00001

diff --git a/blenderkit/thumbnails/vs_validated.png b/blenderkit/thumbnails/vs_validated.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2d8fdd297ab4d970ad7df8748dcfcd91e05c4d9
GIT binary patch
literal 2393
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

literal 0
HcmV?d00001

diff --git a/blenderkit/ui.py b/blenderkit/ui.py
index 7117055fa..7ef5092a9 100644
--- a/blenderkit/ui.py
+++ b/blenderkit/ui.py
@@ -360,8 +360,10 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
     if gravatar is not None:
         # ui_bgl.draw_image(x + isizex - gsize - textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
         #                   gsize, gsize, gravatar, 1)
-        ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
-                          gsize, gsize, gravatar, 1)
+        # ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
+        #                   gsize, gsize, gravatar, 1)
+        ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - textmargin,
+                      gsize, gsize, gravatar, 1)
 
     i = 0
     column_lines = -1  # start minus one for the name
@@ -399,13 +401,14 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
                 xtext -= gsize + textmargin
 
         ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
-        if False:  # i == 0:
+        if i == 0:
+            fsize = name_height-4
             ytext = y - name_height + 5 - isizey + texth - textmargin
         elif i == len(lines) - 1:
             ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
             tcol = textcol
             tsize = font_height
-        if (i > 0 and alines[i - 1][:7] == 'Author:'):
+        elif (i > 0 and alines[i - 1][:7] == 'Author:'):
             tcol = textcol_strong
             fsize = font_height + 2
         else:
@@ -455,9 +458,16 @@ def draw_tooltip_with_author(asset_data, x, y):
                 gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
             atip = a['tooltip']
 
+
+    tooltip = f"{asset_data['displayName']}\n\n" \
+              f"Left click to drag to append/link.\nRight click for more."
+
     # scene = bpy.context.scene
     # ui_props = scene.blenderkitUI
-    draw_tooltip(x, y, text=asset_data['tooltip'], author=atip, img=img,
+    author_s = ''
+    if not gimg:
+        author_s = 'Author: '
+    draw_tooltip(x, y, text=asset_data['displayName']+'\n\n', author=f"{author_s}{a['firstName']} {a['lastName']}", img=img,
                  gravatar=gimg)
 
 
@@ -851,6 +861,8 @@ def draw_asset_bar(self, context):
                         img = utils.get_thumbnail('locked.png')
                         ui_bgl.draw_image(x + 2, y + 2, 24, 24, img, 1)
 
+                    # pcoll = icons.icon_collections["main"]
+                    # v_icon = pcoll['rejected']
                     v_icon = verification_icons[result.get('verificationStatus', 'validated')]
                     if v_icon is not None:
                         img = utils.get_thumbnail(v_icon)
@@ -1575,8 +1587,12 @@ class AssetBarOperator(bpy.types.Operator):
             my = event.mouse_y - r.y
 
             if event.value == 'PRESS' and mouse_in_asset_bar(mx, my):
-                # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
-                bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
+                context.window.cursor_warp(event.mouse_x-500, event.mouse_y-45);
+
+                bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
+                context.window.cursor_warp(event.mouse_x, event.mouse_y);
+
+                # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
                 return {'RUNNING_MODAL'}
 
         if event.type == 'LEFTMOUSE':
@@ -1960,7 +1976,7 @@ def find_and_activate_instancers(object):
 
 
 class AssetDragOperator(bpy.types.Operator):
-    """Draw a line with the mouse"""
+    """Drag & drop assets into scene."""
     bl_idname = "view3d.asset_drag_drop"
     bl_label = "BlenderKit asset drag drop"
 
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
index 6bacc895c..4f703ba3f 100644
--- a/blenderkit/ui_panels.py
+++ b/blenderkit/ui_panels.py
@@ -17,8 +17,9 @@
 # ##### END GPL LICENSE BLOCK #####
 
 
-from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions, ui, tasks_queue, \
-    autothumb
+from blenderkit import paths, ratings, ratings_utils, utils, download, categories, icons, search, resolutions, ui, \
+    tasks_queue, \
+    autothumb, upload
 
 from bpy.types import (
     Panel
@@ -35,9 +36,7 @@ from bpy.props import (
 
 import bpy
 import os
-import random
 import logging
-import blenderkit
 
 bk_logger = logging.getLogger('blenderkit')
 
@@ -207,7 +206,6 @@ def draw_panel_hdr_search(self, context):
     utils.label_multiline(layout, text=props.report)
 
 
-
 def draw_thumbnail_upload_panel(layout, props):
     update = False
     tex = autothumb.get_texture_ui(props.thumbnail, '.upload_preview')
@@ -216,6 +214,7 @@ def draw_thumbnail_upload_panel(layout, props):
     box = layout.box()
     box.template_icon(icon_value=tex.image.preview.icon_id, scale=6.0)
 
+
 def draw_panel_model_upload(self, context):
     ob = bpy.context.active_object
     while ob.parent is not None:
@@ -646,7 +645,8 @@ def draw_panel_material_upload(self, context):
     prop_needed(row, props, 'thumbnail', props.has_thumbnail, False)
 
     if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'):
-        layout.operator("object.blenderkit_generate_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT')
+        layout.operator("object.blenderkit_generate_material_thumbnail", text='Render thumbnail with Cycles',
+                        icon='EXPORT')
     if props.is_generating_thumbnail:
         row = layout.row(align=True)
         row.label(text=props.thumbnail_generating_state, icon='RENDER_STILL')
@@ -1153,12 +1153,13 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
 
     layout.operator_context = 'INVOKE_DEFAULT'
 
-    op = layout.operator('wm.blenderkit_menu_rating_upload', text='Rate')
-    op.asset_name = asset_data['name']
-    op.asset_id = asset_data['id']
-    op.asset_type = asset_data['assetType']
+    if from_panel:
+        op = layout.operator('wm.blenderkit_menu_rating_upload', text='Rate')
+        op.asset_name = asset_data['name']
+        op.asset_id = asset_data['id']
+        op.asset_type = asset_data['assetType']
 
-    if wm.get('bkit authors') is not None and author_id is not None:
+    if from_panel and wm.get('bkit authors') is not None and author_id is not None:
         a = bpy.context.window_manager['bkit authors'].get(author_id)
         if a is not None:
             # utils.p('author:', a)
@@ -1172,6 +1173,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
             op.author_id = author_id
 
     op = layout.operator('view3d.blenderkit_search', text='Search Similar')
+    op.tooltip = 'Search for similar assets in the library'
     # build search string from description and tags:
     op.keywords = asset_data['name']
     if asset_data.get('description'):
@@ -1263,7 +1265,6 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
                         op.model_rotation = (0, 0, 0)
                 op.max_resolution = asset_data.get('max_resolution',
                                                    0)  # str(utils.get_param(asset_data, 'textureResolutionMax'))
-                print('should be drawn!')
             # print('operator res ', resolution)
             # op.resolution = resolution
 
@@ -1297,7 +1298,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
 
             row = layout.row()
             row.operator_context = 'INVOKE_DEFAULT'
-            op = layout.operator('wm.blenderkit_fast_metadata', text='Fast Edit Metadata')
+            op = layout.operator('wm.blenderkit_fast_metadata', text='Edit Metadata')
             op.asset_id = asset_data['id']
             op.asset_type = asset_data['assetType']
 
@@ -1377,97 +1378,429 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu):
         asset_data = sr[ui_props.active_index]
         draw_asset_context_menu(self.layout, context, asset_data, from_panel=False)
 
-        # ui_props = context.scene.blenderkitUI
-        #
-        # sr = bpy.context.window_manager['search results']
-        # asset_data = sr[ui_props.active_index]
-        # layout = self.layout
-        # row = layout.row()
-        # split = row.split(factor=0.2)
-        # col = split.column()
-        # op = col.operator('view3d.asset_drag_drop')
-        # op.asset_search_index=ui_props.active_index
-        #
-        # draw_asset_context_menu(col, context, asset_data, from_panel=False)
-        # split = split.split(factor=0.3)
-        # col1 = split.column()
-        # box = col1.box()
-        # utils.label_multiline(box, asset_data['tooltip'])
-        # col2 = split.column()
-        #
-        # pcoll = icons.icon_collections["main"]
-        # my_icon = pcoll['test']
-        # row = col2.row()
-        # row.scale_y = 4
-        # row.template_icon(icon_value=my_icon.icon_id, scale=2.0)
-        # # col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0)
-        # box2 = col2.box()
-        #
-        # box2.label(text='and heere goes the rating')
-        # box2.label(text='************')
-        # box2.label(text='dadydadadada')
-
 
 class AssetPopupCard(bpy.types.Operator):
     """Generate Cycles thumbnail for model assets"""
     bl_idname = "wm.blenderkit_asset_popup"
     bl_label = "BlenderKit asset popup"
-    # bl_options = {'REGISTER', 'INTERNAL'}
-    bl_options = {'REGISTER', }
+
+    width = 700
+
+    message: StringProperty(
+        name="message",
+        description="message",
+        default="Rating asset",
+        options={'SKIP_SAVE'})
+
+    asset_id: StringProperty(
+        name="Asset Base Id",
+        description="Unique id of the asset (hidden)",
+        default="",
+        options={'SKIP_SAVE'})
+
+    asset_name: StringProperty(
+        name="Asset Name",
+        description="Name of the asset (hidden)",
+        default="",
+        options={'SKIP_SAVE'})
+
+    asset_type: StringProperty(
+        name="Asset type",
+        description="asset type",
+        default="",
+        options={'SKIP_SAVE'})
+
+    rating_quality: IntProperty(name="Quality",
+                                description="quality of the material",
+                                default=0,
+                                min=-1, max=10,
+                                # update=update_ratings_quality,
+                                options={'SKIP_SAVE'})
+
+    # the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily.
+    rating_quality_ui: EnumProperty(name='rating_quality_ui',
+                                    items=ratings_utils.stars_enum_callback,
+                                    description='Rating stars 0 - 10',
+                                    default=0,
+                                    update=ratings_utils.update_quality_ui,
+                                    options={'SKIP_SAVE'})
+
+    rating_work_hours: FloatProperty(name="Work Hours",
+                                     description="How many hours did this work take?",
+                                     default=0.00,
+                                     min=0.0, max=300,
+                                     # update=update_ratings_work_hours,
+                                     options={'SKIP_SAVE'}
+                                     )
+
+    high_rating_warning = "This is a high rating, please be sure to give such rating only to amazing assets"
+
+    rating_work_hours_ui: EnumProperty(name="Work Hours",
+                                       description="How many hours did this work take?",
+                                       items=[('0', '0', ''),
+                                              ('.5', '0.5', ''),
+                                              ('1', '1', ''),
+                                              ('2', '2', ''),
+                                              ('3', '3', ''),
+                                              ('4', '4', ''),
+                                              ('5', '5', ''),
+                                              ('6', '6', ''),
+                                              ('8', '8', ''),
+                                              ('10', '10', ''),
+                                              ('15', '15', ''),
+                                              ('20', '20', ''),
+                                              ('30', '30', high_rating_warning),
+                                              ('50', '50', high_rating_warning),
+                                              ('100', '100', high_rating_warning),
+                                              ('150', '150', high_rating_warning),
+                                              ('200', '200', high_rating_warning),
+                                              ('250', '250', high_rating_warning),
+                                              ],
+                                       default='0', update=ratings_utils.update_ratings_work_hours_ui,
+                                       options={'SKIP_SAVE'}
+                                       )
+
+    rating_work_hours_ui_1_5: EnumProperty(name="Work Hours",
+                                           description="How many hours did this work take?",
+                                           items=[('0', '0', ''),
+                                                  ('.2', '0.2', ''),
+                                                  ('.5', '0.5', ''),
+                                                  ('1', '1', ''),
+                                                  ('2', '2', ''),
+                                                  ('3', '3', ''),
+                                                  ('4', '4', ''),
+                                                  ('5', '5', '')
+                                                  ],
+                                           default='0',
+                                           update=ratings_utils.update_ratings_work_hours_ui_1_5,
+                                           options={'SKIP_SAVE'}
+                                           )
+
+    rating_work_hours_ui_1_10: EnumProperty(name="Work Hours",
+                                            description="How many hours did this work take?",
+                                            items=[('0', '0', ''),
+                                                   ('1', '1', ''),
+                                                   ('2', '2', ''),
+                                                   ('3', '3', ''),
+                                                   ('4', '4', ''),
+                                                   ('5', '5', ''),
+                                                   ('6', '6', ''),
+                                                   ('7', '7', ''),
+                                                   ('8', '8', ''),
+                                                   ('9', '9', ''),
+                                                   ('10', '10', '')
+                                                   ],
+                                            default='0',
+                                            update=ratings_utils.update_ratings_work_hours_ui_1_10,
+                                            options={'SKIP_SAVE'}
+                                            )
 
     @classmethod
     def poll(cls, context):
         return True
 
+    def draw_menu(self, context, layout):
+        ui_props = context.scene.blenderkitUI
+
+        col_left = layout.column()
+        # row_button = col_left.row()
+        # row_button.scale_y = 3
+        # op = row_button.operator('view3d.asset_drag_drop', text='Drag & Drop')
+        # op.asset_search_index = ui_props.active_index
+        draw_asset_context_menu(col_left, context, self.asset_data, from_panel=False)
+        # layout = col_left
+        # op = layout.operator('view3d.blenderkit_search', text='Search Similar')
+        # # build search string from description and tags:
+        # op.keywords = self.asset_data['name']
+        # if self.asset_data.get('description'):
+        #     op.keywords += ' ' + self.asset_data.get('description') + ' '
+        # op.keywords += ' '.join(self.asset_data.get('tags'))
+
+    def draw_property(self, layout, left, right, icon=None, icon_value=None, url=None, tooltip=''):
+        right = str(right)
+        row = layout.row()
+        split = row.split(factor=0.4)
+        split.alignment = 'RIGHT'
+        split.label(text=left)
+        split = split.split()
+        # if url:
+        #     if icon_value:
+        #         op = split.operator('wm.url_open', text=right, icon_value=icon_value)
+        #     elif icon:
+        #         op = split.operator('wm.url_open', text=right, icon=icon)
+        #     else:
+        #         op = split.operator('wm.url_open', text=right)
+        #     op.url = url
+        #     return
+        if url:
+            split = split.split(factor=0.9)
+        if icon_value:
+            split.label(text=right, icon_value=icon_value)
+        elif icon:
+            split.label(text=right, icon=icon)
+
+        else:
+            split.label(text=right)
+        if url:
+            split = split.split()
+            op = split.operator('wm.blenderkit_url', text='', icon='QUESTION')
+            op.url = url
+            op.tooltip = tooltip
+
+    def draw_asset_parameter(self, layout, key='', pretext=''):
+        parameter = utils.get_param(self.asset_data, key)
+        if parameter == None:
+            return
+        self.draw_property(layout, pretext, parameter)
+
+    def draw_tooltip(self, layout):
+        if type(self.asset_data['parameters']) == list:
+            mparams = utils.params_to_dict(self.asset_data['parameters'])
+        else:
+            mparams = self.asset_data['parameters']
+
+        layout = layout.column()
+        if len(self.asset_data['description']) > 0:
+            box = layout.box()
+            box.scale_y = 0.8
+            box.label(text='Description:')
+            utils.label_multiline(box, self.asset_data['description'], width=200)
+
+        pcoll = icons.icon_collections["main"]
+
+        box = layout.box()
+        box.scale_y = 0.8
+
+        if self.asset_data.get('license') == 'cc_zero':
+            t = 'CC Zero'
+            icon = pcoll['cc0']
+
+        else:
+            t = 'Royalty free'
+            icon = pcoll['royalty_free']
+
+        self.draw_property(box,
+                           'license:', t,
+                           icon_value=icon.icon_id,
+                           url="https://www.blenderkit.com/docs/licenses/",
+                           tooltip='All BlenderKit assets are available for commercial use. '
+                                   'Click to read more about BlenderKit licenses online'
+                           )
+
+        if upload.can_edit_asset(asset_data=self.asset_data):
+            icon = pcoll[self.asset_data['verificationStatus']]
+            verification_status_tooltips = {
+                'uploading': "Your asset got stuck during upload. Probably, your file was too large "
+                             "or your connection too slow or interrupting. If you have repeated issues, "
+                             "please contact us and let us know, it might be a bug",
+                'uploaded': "Your asset uploaded successfully. Yay! If it's public, "
+                            "it's awaiting validation. If it's private, use it",
+                'on_hold': "Your asset needs some (usually smaller) fixes, "
+                           "so we can make it public for everybody."
+                           " Please check your email to see the feedback "
+                           "that we send to every creator personally",
+                'rejected': "The asset has serious quality issues, " \
+                            "and it's probable that it might be good to start " \
+                            "all over again or try with something simpler. " \
+                            "You also get personal feedback into your e-mail, " \
+                            "since we believe that together, we can all learn " \
+                            "to become awesome 3D artists",
+                'deleted': "You deleted this asset",
+                'validated': "Your asset passed our validation process, "
+                             "and is now available to BlenderKit users"
+
+            }
+            self.draw_property(box,
+                               'Verification:',
+                               self.asset_data['verificationStatus'],
+                               icon_value=icon.icon_id,
+                               url="https://www.blenderkit.com/docs/validation-status/",
+                               tooltip=verification_status_tooltips[self.asset_data['verificationStatus']]
+
+                               )
+
+        self.draw_asset_parameter(box, key='textureResolutionMax', pretext='Resolution:')
+
+        self.draw_asset_parameter(box, key='designer', pretext='Designer:')
+        self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer:')
+        self.draw_asset_parameter(box, key='collection', pretext='Collection:')
+        self.draw_asset_parameter(box, key='designYear', pretext='Design year:')
+        self.draw_asset_parameter(box, key='faceCount', pretext='Face count:')
+        self.draw_asset_parameter(box, key='thumbnailScale', pretext='Preview scale:')
+
+        if utils.get_param(self.asset_data, 'dimensionX'):
+            t = '%s × %s × %s m' % (utils.fmt_length(mparams['dimensionX']),
+                                    utils.fmt_length(mparams['dimensionY']),
+                                    utils.fmt_length(mparams['dimensionZ']))
+            self.draw_property(box, 'Size:', t)
+
+        #Free/Full plan or private Access
+        if self.asset_data['isPrivate']:
+            t = 'Private'
+            self.draw_property(box, 'Access:', t, icon='LOCKED')
+        elif self.asset_data['isFree']:
+            t = 'Free plan'
+            icon = pcoll['free']
+            self.draw_property(box, 'Access:', t, icon_value=icon.icon_id)
+        else:
+            t = 'Full plan'
+            icon = pcoll['full']
+            self.draw_property(box, 'Access:', t, icon_value=icon.icon_id)
+
+    def draw_author(self, layout, width=330):
+        image_split = 0.25
+        text_width = width
+        authors = bpy.context.window_manager['bkit authors']
+        a = authors.get(self.asset_data['author']['id'])
+        if a is not None:  # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
+            row = layout.row()
+            author_box = row.box()
+            author_box.scale_y = 0.6  # get text lines closer to each other
+            if hasattr(self, 'gimg'):
+
+                author_left = author_box.split(factor=0.25)
+                author_left.template_icon(icon_value=self.gimg.preview.icon_id, scale=6.0)
+                text_area = author_left.split()
+                text_width = int(text_width * (1 - image_split))
+            else:
+                text_area = author_box
+
+            author_right = text_area.column()
+            row = author_right.row()
+            col = row.column()
+
+            utils.label_multiline(col, text=a['tooltip'], width=text_width)
+            if upload.can_edit_asset(asset_data=self.asset_data) and a.get('aboutMe') is not None and len(
+                    a.get('aboutMe', '')) == 0:
+                col.label(text='Please write something about yourself!')
+                op = col.operator('wm.blenderkit_url', text='Edit your profile')
+                op.url = 'https://www.blenderkit.com/profile'
+                op.tooltip = 'Edit your profile on BlenderKit webpage'
+
+            button_row = author_box.row()
+            button_row.scale_y = 2.0
+
+            if a.get('aboutMeUrl') is not None:
+                url = a['aboutMeUrl']
+                text = url
+                if len(url) > 25:
+                    text = url[:25] + '...'
+            else:
+                url = paths.get_author_gallery_url(a['id'])
+                text = "Open Author's Profile"
+
+            op = button_row.operator('wm.url_open', text=text)
+            op.url = url
+
+            op = button_row.operator('view3d.blenderkit_search', text="Show Assets By Author")
+            op.keywords = ''
+            op.author_id = self.asset_data['author']['id']
+
+    def draw_thumbnail_box(self, layout):
+        layout.emboss = 'NORMAL'
+
+        box_thumbnail = layout.box()
+        # row = split_right.row()
+        # column_right = row.column()
+
+        box_thumbnail.scale_y = 0.5
+        # row = box_thumbnail.row()
+        # row.scale_y = 20
+
+        box_thumbnail.template_icon(icon_value=self.img.preview.icon_id, scale=34.0)
+        # row = box_thumbnail.row()
+        # row.scale_y = 4
+        # op = row.operator('view3d.asset_drag_drop', text='Drag & Drop from here', depress=True)
+
+        row = box_thumbnail.row()
+        row.alignment = 'EXPAND'
+        rc = self.asset_data.get('ratingsCount')
+        show_rating_threshold = 3
+
+        if rc:
+            rcount = min(rc['quality'], rc['workingHours'])
+        else:
+            rcount = 0
+        if rcount >= show_rating_threshold or upload.can_edit_asset(asset_data=self.asset_data):
+            pcoll = icons.icon_collections["main"]
+            my_icon = pcoll['trophy']
+            s = self.asset_data['score']
+            if s:
+                row.label(text=str(round(s)), icon_value=my_icon.icon_id)
+            q = self.asset_data['ratingsAverage'].get('quality')
+            if q:
+                row.label(text=str(round(q)), icon='SOLO_ON')
+            c = self.asset_data['ratingsAverage'].get('workingHours')
+            if c:
+                row.label(text=str(round(c)), icon='SORTTIME')
+        else:
+            box_thumbnail.label(text=f"This asset needs more ratings ( {rcount} of {show_rating_threshold} ).")
+            # box_thumbnail.label(text=f"Please rate this asset.")
+
+    def draw_menu_desc_author(self, context, layout):
+        box = layout.column()
+
+        box.emboss = 'NORMAL'
+        # left - tooltip & params
+        row = box.row()
+        split_left_left = row.split(factor=0.7)
+        self.draw_tooltip(split_left_left)
+
+        # right - menu
+        col1 = split_left_left.split()
+        self.draw_menu(context, col1)
+
+        # author
+        self.draw_author(box)
+
     def draw(self, context):
         ui_props = context.scene.blenderkitUI
 
         sr = bpy.context.window_manager['search results']
         asset_data = sr[ui_props.active_index]
+        self.asset_data = asset_data
         layout = self.layout
-        row = layout.row()
-        split = row.split(factor=0.2)
-        col = split.column()
-        op = col.operator('view3d.asset_drag_drop')
-        op.asset_search_index = ui_props.active_index
-        draw_asset_context_menu(col, context, asset_data, from_panel=False)
-        split = split.split(factor=0.5)
-        col1 = split.column()
-        box = col1.box()
-        utils.label_multiline(box, asset_data['tooltip'], width=300)
-
-        col2 = split.column()
+        # top draggabe bar with name of the asset
+        top_row = layout.row()
+        top_drag_bar = top_row.box()
+        top_drag_bar.alignment = 'CENTER'
 
-        pcoll = icons.icon_collections["main"]
-        my_icon = pcoll['test']
-        col2.template_icon(icon_value=my_icon.icon_id, scale=20.0)
-        # col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0)
-        box2 = col2.box()
-
-        # draw_ratings(box2, context, asset_data)
-        box2.label(text='Ratings')
-        # print(tp, dir(tp))
-        # if not hasattr(self, 'first_draw'):# try to redraw because of template preview which needs update
-        #     for region in context.area.regions:
-        #         region.tag_redraw()
-        #     self.first_draw = True
+        top_drag_bar.label(text=asset_data['displayName'])
+        # left side
+        row = layout.row(align=True)
+        split_left = row.split(factor=0.5)
+        self.draw_thumbnail_box(split_left)
 
-    def execute(self, context):
-        print('execute')
-        return {'FINISHED'}
+        # right split
+        split_right = split_left.split()
+        self.draw_menu_desc_author(context, split_right)
 
-    def invoke(self, context, event):
+        ratings_box = layout.box()
+        ratings_box.label(text='Rate asset quality:')
+        ratings.draw_ratings_menu(self, context, ratings_box)
+        tip_box = layout.box()
+        tip_box.label(text=self.tip)
+
+    def execute(self, context):
         wm = context.window_manager
         ui_props = context.scene.blenderkitUI
         ui_props.draw_tooltip = False
         sr = bpy.context.window_manager['search results']
         asset_data = sr[ui_props.active_index]
         self.img = ui.get_large_thumbnail_image(asset_data)
+        self.asset_type = asset_data['assetType']
         # self.tex = utils.get_hidden_texture(self.img)
         # self.tex.update_tag()
 
+        authors = bpy.context.window_manager['bkit authors']
+        a = authors.get(asset_data['author']['id'])
+        if a.get('gravatarImg') is not None:
+            self.gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
+
         bl_label = asset_data['name']
-        return wm.invoke_props_dialog(self, width=700)
+        self.tip = search.get_random_tip()
+        self.tip = self.tip.replace('\n', '')
+        return wm.invoke_popup(self, width=self.width)
 
 
 class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu):
diff --git a/blenderkit/upload.py b/blenderkit/upload.py
index cc72b92a6..b1d20f944 100644
--- a/blenderkit/upload.py
+++ b/blenderkit/upload.py
@@ -580,13 +580,13 @@ def can_edit_asset(active_index=-1, asset_data=None):
         sr = bpy.context.window_manager['search results']
         asset_data = dict(sr[active_index])
     # print(profile, asset_data)
-    if asset_data['author']['id'] == profile['user']['id']:
+    if int(asset_data['author']['id']) == int(profile['user']['id']):
         return True
     return False
 
 
 class FastMetadata(bpy.types.Operator):
-    """Fast change of the category of object directly in asset bar."""
+    """Edit metadata of the asset"""
     bl_idname = "wm.blenderkit_fast_metadata"
     bl_label = "Update metadata"
     bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
@@ -731,6 +731,7 @@ class FastMetadata(bpy.types.Operator):
             active_asset = utils.get_active_asset_by_type(asset_type = self.asset_type)
             asset_data = active_asset.get('asset_data')
 
+        print('can edit asset?', can_edit_asset(asset_data=asset_data))
         if not can_edit_asset(asset_data=asset_data):
             return {'CANCELLED'}
         self.asset_id = asset_data['id']
@@ -1298,7 +1299,7 @@ class AssetDebugPrint(Operator):
 class AssetVerificationStatusChange(Operator):
     """Change verification status"""
     bl_idname = "object.blenderkit_change_status"
-    bl_description = "Change asset ststus"
+    bl_description = "Change asset status"
     bl_label = "Change verification status"
     bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
 
diff --git a/blenderkit/utils.py b/blenderkit/utils.py
index 953ded5e5..f4302ac0b 100644
--- a/blenderkit/utils.py
+++ b/blenderkit/utils.py
@@ -337,6 +337,12 @@ def get_hidden_texture(name, force_reload=False):
     return t
 
 
+def img_to_preview(img):
+    img.preview.image_size = (img.size[0], img.size[1])
+    img.preview.image_pixels_float = img.pixels[:]
+    # img.preview.icon_size = (img.size[0], img.size[1])
+    # img.preview.icon_pixels_float = img.pixels[:]
+
 def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
     if bdata_name[0] == '.':
         hidden_name = bdata_name
@@ -355,6 +361,7 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
 
         if img is None:
             img = bpy.data.images.load(tpath)
+            img_to_preview(img)
             img.name = hidden_name
         else:
             if img.filepath != tpath:
@@ -363,13 +370,16 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
 
                 img.filepath = tpath
                 img.reload()
+                img_to_preview(img)
         image_utils.set_colorspace(img, colorspace)
 
     elif force_reload:
         if img.packed_file is not None:
             img.unpack(method='USE_ORIGINAL')
         img.reload()
+        img_to_preview(img)
         image_utils.set_colorspace(img, colorspace)
+
     return img
 
 
@@ -690,6 +700,9 @@ def name_update(props):
         # Here we actually rename assets datablocks, but don't do that with HDR's and possibly with others
         asset.name = fname
 
+def fmt_length(prop):
+    prop = str(round(prop, 2))
+    return prop
 
 def get_param(asset_data, parameter_name, default = None):
     if not asset_data.get('parameters'):
-- 
GitLab