BlenderKit: Fix rating updates for panel.

Fix a bug in upload when checking image /procedural assets
Fix oauth return values in case of error
Fix fetching of authors through search api instead of profiles.
Fix task_queue with multiple tasks of the same by enabling stashing
Fix selected asset panel, rename it to selected model by now (not supporting other assets now)
This commit is contained in:
Vilém Duha 2020-01-31 14:45:46 +01:00
parent 55824952f5
commit 168f144256
7 changed files with 163 additions and 88 deletions

View File

@ -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",

View File

@ -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])

View File

@ -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']

View File

@ -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):

View File

@ -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

View File

@ -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()

View File

@ -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):