diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py
index bd9f7e3fb7bd1da98f5c8d9b1821cb843754d970..9db72bb04bf88820353af8cd133d14ae5666bb93 100644
--- a/blenderkit/__init__.py
+++ b/blenderkit/__init__.py
@@ -385,11 +385,13 @@ class BlenderKitUIProps(PropertyGroup):
     dragging_rating_work_hours: BoolProperty(name="Dragging Rating Work Hours", default=False)
     last_rating_time: FloatProperty(name="Last Rating Time", default=0.0)
 
-def search_procedural_update(self,context):
+
+def search_procedural_update(self, context):
     if self.search_procedural in ('PROCEDURAL', 'BOTH'):
         self.search_texture_resolution = False
     search.search_update(self, context)
 
+
 class BlenderKitCommonSearchProps(object):
     # STATES
     is_searching: BoolProperty(name="Searching", description="search is currently running (internal)", default=False)
@@ -645,9 +647,16 @@ class BlenderKitCommonUploadProps(object):
 
 
 class BlenderKitRatingProps(PropertyGroup):
-    rating_quality: IntProperty(name="Quality", description="quality of the material", default=0, min=-1, max=10)
-    rating_work_hours: FloatProperty(name="Work Hours", description="How many hours did this work take?", default=0.01,
-                                     min=0.0, max=1000
+    rating_quality: IntProperty(name="Quality",
+                                description="quality of the material",
+                                default=0,
+                                min=-1, max=10,
+                                update=ratings.update_ratings_quality)
+
+    rating_work_hours: FloatProperty(name="Work Hours",
+                                     description="How many hours did this work take?",
+                                     default=0.01,
+                                     min=0.0, max=1000, update=ratings.update_ratings_work_hours
                                      )
     rating_complexity: IntProperty(name="Complexity",
                                    description="Complexity is a number estimating how much work was spent on the asset.aaa",
diff --git a/blenderkit/asset_inspector.py b/blenderkit/asset_inspector.py
index e6fdc65938ec1ad3167a70e5fbf318bae78deec2..0e26479f3f7fe467714d41f0c3c78f31fd66bef0 100644
--- a/blenderkit/asset_inspector.py
+++ b/blenderkit/asset_inspector.py
@@ -118,9 +118,12 @@ def check_render_engine(props, obs):
                         if n.type not in shaders:
                             shaders.append(n.type)
                     if n.type == 'TEX_IMAGE':
-                        mattype = 'image based'
-                        props.is_procedural = False
-                        if n.image not in textures:
+
+
+                        if n.image is not None and n.image not in textures:
+                            props.is_procedural = False
+                            mattype = 'image based'
+
                             textures.append(n.image)
                             props.texture_count += 1
                             props.total_megapixels += (n.image.size[0] * n.image.size[1])
diff --git a/blenderkit/oauth.py b/blenderkit/oauth.py
index afbd8f6554713b5fe912107ae17402633c80a2e7..95c6bae625bb778fae3c86024ca89a7f713296dd 100644
--- a/blenderkit/oauth.py
+++ b/blenderkit/oauth.py
@@ -52,7 +52,7 @@ class SimpleOAuthAuthenticator(object):
         if response.status_code != 200:
             print("error retrieving refresh tokens %s" % response.status_code)
             print(response.content)
-            return None, None
+            return None, None, None
 
         response_json = json.loads(response.content)
         refresh_token = response_json ['refresh_token']
diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py
index 96cbc01f91868d9fdfc054be979255b4961d1e68..fdbdea7934da7be8b48c19aa6eadb80be1a41357 100644
--- a/blenderkit/ratings.py
+++ b/blenderkit/ratings.py
@@ -22,8 +22,9 @@ if "bpy" in locals():
     paths = reload(paths)
     utils = reload(utils)
     rerequests = reload(rerequests)
+    tasks_queue = reload(tasks_queue)
 else:
-    from blenderkit import paths, utils, rerequests
+    from blenderkit import paths, utils, rerequests, tasks_queue
 
 import bpy
 import requests, threading
@@ -44,12 +45,7 @@ from bpy.types import (
 
 def pretty_print_POST(req):
     """
-    At this point it is completely built and ready
-    to be fired; it is "prepared".
-
-    However pay attention at the formatting used in
-    this function because it is programmed to be pretty
-    printed and may differ from the actual request.
+    pretty print a request
     """
     print('{}\n{}\n{}\n\n{}'.format(
         '-----------START-----------',
@@ -60,6 +56,8 @@ def pretty_print_POST(req):
 
 
 def uplaod_rating_thread(url, ratings, headers):
+    ''' Upload rating thread function / disconnected from blender data.'''
+    utils.p('upload rating', url, ratings)
     for rating_name, score in ratings:
         if (score != -1 and score != 0):
             rating_url = url + rating_name + '/'
@@ -74,12 +72,26 @@ def uplaod_rating_thread(url, ratings, headers):
                 print('ratings upload failed: %s' % str(e))
 
 
+def send_rating_to_thread_quality(url, ratings, headers):
+    '''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=uplaod_rating_thread, args=(url, ratings, headers))
+    thread.start()
+
+def send_rating_to_thread_work_hours(url, ratings, headers):
+    '''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=uplaod_rating_thread, args=(url, ratings, headers))
+    thread.start()
+
+
 def uplaod_review_thread(url, reviews, headers):
     r = rerequests.put(url, data=reviews, verify=True, headers=headers)
 
     # except requests.exceptions.RequestException as e:
     #     print('reviews upload failed: %s' % str(e))
 
+
 def get_rating(asset_id):
     user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
     api_key = user_preferences.api_key
@@ -88,11 +100,38 @@ def get_rating(asset_id):
     rtypes = ['quality', 'working_hours']
     for rt in rtypes:
         params = {
-            'rating_type' : rt
+            'rating_type': rt
         }
         r = rerequests.get(r1, params=data, verify=True, headers=headers)
         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
+    bkit_ratings = asset.bkit_ratings
+    url = paths.get_api_url() + 'assets/' + asset['asset_data']['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=1, 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
+    bkit_ratings = asset.bkit_ratings
+    url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
+
+    if bkit_ratings.rating_quality > 0.1:
+        ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))]
+        tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=1, only_last=True)
+
+
 def upload_rating(asset):
     user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
     api_key = user_preferences.api_key
@@ -134,8 +173,8 @@ def upload_rating(asset):
 class StarRatingOperator(bpy.types.Operator):
     """Tooltip"""
     bl_idname = "object.blenderkit_rating"
-    bl_label = "Rate the Asset"
-    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+    bl_label = "Rate the Asset Quality"
+    bl_options = {'REGISTER', 'INTERNAL'}
 
     property_name: StringProperty(
         name="Rating Property",
@@ -148,7 +187,7 @@ class StarRatingOperator(bpy.types.Operator):
     def execute(self, context):
         asset = utils.get_active_asset()
         props = asset.bkit_ratings
-        props[self.property_name] = self.rating
+        props.rating_quality = self.rating
         return {'FINISHED'}
 
 
@@ -162,6 +201,7 @@ asset_types = (
 )
 
 
+# TODO drop this operator, not needed anymore.
 class UploadRatingOperator(bpy.types.Operator):
     """Upload rating to the web db"""
     bl_idname = "object.blenderkit_rating_upload"
@@ -169,12 +209,12 @@ class UploadRatingOperator(bpy.types.Operator):
     bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
 
     # type of upload - model, material, textures, e.t.c.
-    asset_type: EnumProperty(
-        name="Type",
-        items=asset_types,
-        description="Type of asset",
-        default="MODEL",
-    )
+    # asset_type: EnumProperty(
+    #     name="Type",
+    #     items=asset_types,
+    #     description="Type of asset",
+    #     default="MODEL",
+    # )
 
     # @classmethod
     # def poll(cls, context):
diff --git a/blenderkit/search.py b/blenderkit/search.py
index 56c22dbba5106dffb6f29d94f7ab1eb4abc9668c..f5cadaadb5a2457cca042bdaca06c351aeb24b8b 100644
--- a/blenderkit/search.py
+++ b/blenderkit/search.py
@@ -244,10 +244,10 @@ def timer_update():  # TODO might get moved to handle all blenderkit stuff.
                                               'tags': r['tags'],
                                               'can_download': r.get('canDownload', True),
                                               'verification_status': r['verificationStatus'],
-                                              'author_id': str(r['author']['id'])
+                                              'author_id': str(r['author']['id']),
                                               # 'author': r['author']['firstName'] + ' ' + r['author']['lastName']
                                               # 'description': r['description'],
-                                              # 'author': r['description'],
+                                              'author': r['author'],
                                               }
                                 asset_data['downloaded'] = 0
 
@@ -504,7 +504,7 @@ def generate_tooltip(mdata):
 
     # t += 'uv: %s\n' % mdata['uv']
     # t += '\n'
-    t = writeblockm(t, mdata, key='license', width = col_w)
+    t = writeblockm(t, mdata, key='license', width=col_w)
 
     # generator is for both upload preview and search, this is only after search
     # if mdata.get('versionNumber'):
@@ -606,45 +606,45 @@ class ThumbDownloader(threading.Thread):
             #         f.write(chunk)
 
 
-def write_author(a_id, adata):
-    # utils.p('writing author back')
+def write_gravatar(a_id, gravatar_path):
+    '''
+    Write down gravatar path, as a result of thread-based gravatar image download.
+    This should happen on timer in queue.
+    '''
+    # print('write author', a_id, type(a_id))
     authors = bpy.context.window_manager['bkit authors']
-    if authors.get(a_id) in (None, ''):
-        adata['tooltip'] = generate_author_textblock(adata)
-        authors[a_id] = adata
+    if authors.get(a_id) is not None:
+        adata = authors.get(a_id)
+        adata['gravatarImg'] = gravatar_path
 
 
-def fetch_author(a_id, api_key):
-    utils.p('fetch author')
-    try:
-        a_url = paths.get_api_url() + 'accounts/' + a_id + '/'
-        headers = utils.get_headers(api_key)
-        r = rerequests.get(a_url, headers=headers)
+def fetch_gravatar(adata):
+    utils.p('fetch gravatar')
+    if adata.get('gravatarHash') is not None:
+        gravatar_path = paths.get_temp_dir(subdir='g/') + adata['gravatarHash'] + '.jpg'
+
+        if os.path.exists(gravatar_path):
+            tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
+            return;
+
+        url = "https://www.gravatar.com/avatar/" + adata['gravatarHash'] + '?d=404'
+        r = rerequests.get(url, stream=False)
         if r.status_code == 200:
-            adata = r.json()
-            if not hasattr(adata, 'id'):
-                utils.p(adata)
-            # utils.p(adata)
-            tasks_queue.add_task((write_author, (a_id, adata)))
-            if adata.get('gravatarHash') is not None:
-                gravatar_path = paths.get_temp_dir(subdir='g/') + adata['gravatarHash'] + '.jpg'
-                url = "https://www.gravatar.com/avatar/" + adata['gravatarHash'] + '?d=404'
-                r = rerequests.get(url, stream=False)
-                if r.status_code == 200:
-                    with open(gravatar_path, 'wb') as f:
-                        f.write(r.content)
-                    adata['gravatarImg'] = gravatar_path
-                elif r.status_code == '404':
-                    adata['gravatarHash'] = None
-                    utils.p('gravatar for author not available.')
-    except Exception as e:
-        utils.p(e)
-    utils.p('finish fetch')
+            with open(gravatar_path, 'wb') as f:
+                f.write(r.content)
+            tasks_queue.add_task((write_gravatar, (adata['id'], gravatar_path)))
+        elif r.status_code == '404':
+            adata['gravatarHash'] = None
+            utils.p('gravatar for author not available.')
 
 
-# profile_counter =0
+fetching_gravatars = {}
+
 
 def get_author(r):
+    ''' Writes author info (now from search results) and fetches gravatar if needed.'''
+    global fetching_gravatars
+
     a_id = str(r['author']['id'])
     preferences = bpy.context.preferences.addons['blenderkit'].preferences
     authors = bpy.context.window_manager.get('bkit authors', {})
@@ -652,12 +652,16 @@ def get_author(r):
         bpy.context.window_manager['bkit authors'] = authors
     a = authors.get(a_id)
     if a is None:  # or a is '' or (a.get('gravatarHash') is not None and a.get('gravatarImg') is None):
-        authors[a_id] = ''
-        thread = threading.Thread(target=fetch_author, args=(a_id, preferences.api_key), daemon=True)
+        a = r['author']
+        a['id'] = a_id
+        a['tooltip'] = generate_author_textblock(a)
+
+        authors[a_id] = a
+        if fetching_gravatars.get(a['id']) is None:
+            fetching_gravatars[a['id']] = True
+
+        thread = threading.Thread(target=fetch_gravatar, args=(a.copy(),), daemon=True)
         thread.start()
-        # global profile_counter
-        # profile_counter+=1
-        # print(profile_counter,'author:', a_id)
     return a
 
 
@@ -846,11 +850,9 @@ class Searcher(threading.Thread):
         thumb_full_urls = []
         thumb_full_filepaths = []
         # END OF PARSING
-        getting_authors = {}
         for d in rdata.get('results', []):
-            if getting_authors.get(d['author']['id']) is None:
-                get_author(d)
-                getting_authors[d['author']['id']] = True
+
+            get_author(d)
 
             for f in d['files']:
                 # TODO move validation of published assets to server, too manmy checks here.
@@ -964,14 +966,13 @@ def build_query_common(query, props):
             # query["procedural"] = False
 
         if props.search_procedural == "PROCEDURAL":
-            #todo this procedural hack should be replaced with the parameter
+            # todo this procedural hack should be replaced with the parameter
             query["files_size_lte"] = 1024 * 1024
             # query["procedural"] = True
         elif props.search_file_size:
             query_common["files_size_gte"] = props.search_file_size_min * 1024 * 1024
             query_common["files_size_lte"] = props.search_file_size_max * 1024 * 1024
 
-
     query.update(query_common)
 
 
@@ -1114,7 +1115,7 @@ def search(category='', get_next=False, author_id=''):
     user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
 
     search_start_time = time.time()
-    #mt('start')
+    # mt('start')
     scene = bpy.context.scene
     uiprops = scene.blenderkitUI
 
diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py
index 3bf2018197732836208ab20287b00c981551b847..95ffb1a688c8a1621557114045a7c23a44bc735c 100644
--- a/blenderkit/tasks_queue.py
+++ b/blenderkit/tasks_queue.py
@@ -43,15 +43,15 @@ def get_queue():
     return t.task_queue
 
 class task_object:
-    def __init__(self, command = '', arguments = (), wait = 0):
+    def __init__(self, command = '', arguments = (), wait = 0, only_last = False):
         self.command = command
         self.arguments = arguments
         self.wait = wait
+        self.only_last = only_last
 
-
-def add_task(task, wait = 0):
+def add_task(task, wait = 0, only_last = False):
     q = get_queue()
-    taskob = task_object(task[0],task[1], wait = wait)
+    taskob = task_object(task[0],task[1], wait = wait, only_last = only_last)
     q.put(taskob)
 
 
@@ -60,6 +60,23 @@ def queue_worker():
     q = get_queue()
 
     back_to_queue = [] #delayed events
+    stashed = {}
+    # first round we get all tasks that are supposed to be stashed and run only once (only_last option)
+    # stashing finds tasks with the property only_last and same command and executes only the last one.
+    while not q.empty():
+        task = q.get()
+        if task.only_last:
+            stashed[task.command] = task
+        else:
+            back_to_queue.append(task)
+    #return tasks to que except for stashed
+    for task in back_to_queue:
+        q.put(task)
+    #return stashed tasks to queue
+    for k in stashed.keys():
+        q.put(stashed[k])
+    #second round, execute or put back waiting tasks.
+    back_to_queue = []
     while not q.empty():
         # print('window manager', bpy.context.window_manager)
         task = q.get()
diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py
index 8bd9fd58cefb3d635df151007c0d1c90d9b2ec83..3dabbe68205ba0dcb9484f3cdd92d1d880f27276 100644
--- a/blenderkit/ui_panels.py
+++ b/blenderkit/ui_panels.py
@@ -81,9 +81,9 @@ def draw_ratings(layout, context):
     # layout.label(text='compliments')
     # layout.prop(bkit_ratings, 'rating_compliments', text='')
 
-    row = layout.row()
-    op = row.operator("object.blenderkit_rating_upload", text="Send rating", icon='URL')
-    return op
+    # row = layout.row()
+    # op = row.operator("object.blenderkit_rating_upload", text="Send rating", icon='URL')
+    # return op
 
 
 def draw_upload_common(layout, props, asset_type, context):
@@ -391,7 +391,7 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
     bl_idname = "VIEW3D_PT_blenderkit_model_properties"
     bl_space_type = 'VIEW_3D'
     bl_region_type = 'UI'
-    bl_label = "Selected Asset"
+    bl_label = "Selected Model"
     bl_context = "objectmode"
 
     @classmethod
@@ -403,7 +403,12 @@ class VIEW3D_PT_blenderkit_model_properties(Panel):
         # draw asset properties here
         layout = self.layout
 
-        o = bpy.context.active_object
+        o = utils.get_active_model()
+        # o = bpy.context.active_object
+        if o.get('asset_data') is None:
+            label_multiline(layout, text='To upload this asset to BlenderKit, go to the Find and Upload Assets pael.')
+            layout.prop(o, 'name')
+
         if o.get('asset_data') is not None:
             ad = o['asset_data']
             layout.label(text=str(ad['name']))
@@ -506,8 +511,8 @@ class VIEW3D_PT_blenderkit_login(Panel):
 
 def draw_panel_model_rating(self, context):
     o = bpy.context.active_object
-    op = draw_ratings(self.layout, context)  # , props)
-    op.asset_type = 'MODEL'
+    draw_ratings(self.layout, context)  # , props)
+    # op.asset_type = 'MODEL'
 
 
 def draw_panel_material_upload(self, context):
@@ -584,7 +589,7 @@ def draw_panel_material_search(self, context):
     if props.search_advanced:
         layout.separator()
 
-        layout.label(text = 'texture types')
+        layout.label(text='texture types')
         col = layout.column()
         col.prop(props, "search_procedural", expand=True)
 
@@ -609,8 +614,8 @@ def draw_panel_material_search(self, context):
 
 
 def draw_panel_material_ratings(self, context):
-    op = draw_ratings(self.layout, context)  # , props)
-    op.asset_type = 'MATERIAL'
+    draw_ratings(self.layout, context)  # , props)
+    # op.asset_type = 'MATERIAL'
 
 
 def draw_panel_brush_upload(self, context):
@@ -643,9 +648,9 @@ def draw_panel_brush_search(self, context):
 
 def draw_panel_brush_ratings(self, context):
     # props = utils.get_brush_props(context)
-    op = draw_ratings(self.layout, context)  # , props)
-
-    op.asset_type = 'BRUSH'
+    draw_ratings(self.layout, context)  # , props)
+    #
+    # op.asset_type = 'BRUSH'
 
 
 def draw_login_buttons(layout):