BlenderKit: new asset bar fixes and improvements

Reports are back and split in reports.py
Quality is drawn in tooltip
comments and notification improvements and fixes
Tips were updated

(cherry picked from commit 3222ff126c)
This commit is contained in:
Vilem Duha 2021-11-05 08:40:56 +01:00
parent 64fcec250d
commit 2259a24adb
18 changed files with 261 additions and 175 deletions

View File

@ -60,6 +60,7 @@ if "bpy" in locals():
upload = reload(upload)
upload_bg = reload(upload_bg)
utils = reload(utils)
reports = reload(reports)
bl_ui_widget = reload(bl_ui_widget)
bl_ui_label = reload(bl_ui_label)
@ -99,6 +100,7 @@ else:
from blenderkit import upload
from blenderkit import upload_bg
from blenderkit import utils
from blenderkit import reports
from blenderkit.bl_ui_widgets import bl_ui_widget
from blenderkit.bl_ui_widgets import bl_ui_label
@ -270,7 +272,7 @@ def switch_search_results(self, context):
wm['search results'] = wm.get('bkit brush search')
wm['search results orig'] = wm.get('bkit brush search orig')
if not (context.sculpt_object or context.image_paint_object):
ui.add_report(
reports.add_report(
'Switch to paint or sculpt mode to search in BlenderKit brushes.')
# if wm['search results'] == None:
# wm['search results'] = []
@ -1658,7 +1660,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
tips_on_start: BoolProperty(
name="Show tips when starting blender",
description="Show tips when starting blender",
default=False
default=True
)
search_in_header: BoolProperty(

View File

@ -24,6 +24,7 @@ from bpy.props import (
StringProperty
)
active_area_pointer = 0
def get_area_height(self):
if type(self.context) != dict:
@ -64,7 +65,7 @@ def modal_inside(self, context, event):
self.finish()
return {'FINISHED'}
self.update_timer +=1
self.update_timer += 1
if self.update_timer > self.update_timer_limit:
self.update_timer = 0
@ -84,7 +85,6 @@ def modal_inside(self, context, event):
else:
asset_button.progress_bar.visible = False
if self.handle_widget_events(event):
return {'RUNNING_MODAL'}
@ -124,11 +124,16 @@ def asset_bar_invoke(self, context, event):
self.register_handlers(args, context)
self.update_timer_limit = 30
self.update_timer =0
self.update_timer = 0
# print('adding timer')
# self._timer = context.window_manager.event_timer_add(10.0, window=context.window)
global active_area_pointer
context.window_manager.modal_handler_add(self)
self.active_window_pointer = context.window.as_pointer()
self.active_area_pointer = context.area.as_pointer()
active_area_pointer = context.area.as_pointer()
self.active_region_pointer = context.region.as_pointer()
return {"RUNNING_MODAL"}
@ -188,7 +193,7 @@ def get_tooltip_data(asset_data):
if rc:
rcount = min(rc.get('quality', 0), rc.get('workingHours', 0))
if rcount > show_rating_threshold:
quality = round(asset_data['ratingsAverage'].get('quality'))
quality = str(round(asset_data['ratingsAverage'].get('quality')))
tooltip_data = {
'aname': aname,
'author_text': author_text,
@ -273,16 +278,32 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.authors_name = authors_name
self.tooltip_widgets.append(authors_name)
gravatar_image = BL_UI_Image(self.tooltip_width - self.gravatar_size, self.tooltip_height - self.gravatar_size, 1, 1)
gravatar_image = BL_UI_Image(self.tooltip_width - self.gravatar_size, self.tooltip_height - self.gravatar_size,
1, 1)
img_path = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
gravatar_image.set_image(img_path)
gravatar_image.set_image_size((self.gravatar_size - 1 * self.margin, self.gravatar_size - 1 * self.margin))
gravatar_image.set_image_position((0, 0))
self.gravatar_image = gravatar_image
self.tooltip_widgets.append(gravatar_image)
offset_y = 16 + self.margin
# label = self.new_text('Left click or drag to append/link. Right click for more options.', self.margin*2, labels_start + offset_y,
# text_size=14)
quality_star = BL_UI_Image(self.margin, self.tooltip_height - self.margin - self.asset_name_text_size,
1, 1)
img_path = paths.get_addon_thumbnail_path('star_grey.png')
quality_star.set_image(img_path)
quality_star.set_image_size((self.asset_name_text_size, self.asset_name_text_size))
quality_star.set_image_position((0, 0))
# self.quality_star = quality_star
self.tooltip_widgets.append(quality_star)
label = self.new_text('', 2*self.margin+self.asset_name_text_size,
self.tooltip_height - int(self.asset_name_text_size+ self.margin * .5),
text_size=self.asset_name_text_size)
self.tooltip_widgets.append(label)
self.quality_label = label
# label = self.new_text('Right click for menu.', self.margin,
# self.tooltip_height - int(self.author_text_size) - self.margin,
# text_size=int(self.author_text_size*.7))
# self.tooltip_widgets.append(label)
def hide_tooltip(self):
@ -383,7 +404,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
if not bpy.context.preferences.system.use_region_overlap:
reg_multiplier = 0
for r in area.regions:
if r.type == 'UI' :
if r.type == 'UI':
ui_width = r.width * reg_multiplier
if r.type == 'TOOLS':
tools_width = r.width * reg_multiplier
@ -409,13 +430,17 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
# self.bar_y = region.height - ui_props.bar_y_offset * ui_scale
self.bar_y = ui_props.bar_y_offset * ui_scale
if ui_props.down_up == 'UPLOAD':
self.reports_y = self.bar_y - 600
self.reports_y = region.height - self.bar_y - 600
ui_props.reports_y = region.height - self.bar_y - 600
self.reports_x = self.bar_x
else:
self.reports_y = self.bar_y - self.bar_height - 100
ui_props.reports_x = self.bar_x
else:#ui.bar_y - ui.bar_height - 100
self.reports_y = region.height - self.bar_y - self.bar_height - 50
ui_props.reports_y =region.height - self.bar_y - self.bar_height- 50
self.reports_x = self.bar_x
ui_props.reports_x = self.bar_x
# print(self.bar_y, self.bar_height, region.height)
def update_layout(self, context, event):
# restarting asset_bar completely since the widgets are too hard to get working with updates.
@ -437,9 +462,10 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.tooltip_panel.width = self.tooltip_width
self.tooltip_image.width = self.tooltip_width
self.tooltip_image.set_image_size((self.tooltip_width, self.tooltip_height))
self.gravatar_image.set_location(self.tooltip_width - self.gravatar_size, self.tooltip_height - self.gravatar_size)
self.gravatar_image.set_location(self.tooltip_width - self.gravatar_size,
self.tooltip_height - self.gravatar_size)
self.authors_name.set_location(self.tooltip_width - self.gravatar_size - self.margin,
self.tooltip_height - self.author_text_size - self.margin)
self.tooltip_height - self.author_text_size - self.margin)
# to hide arrows accordingly
self.scroll_update()
@ -491,6 +517,12 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
new_button.progress_bar = progress_bar
self.progress_bars.append(progress_bar)
if utils.profile_is_validator():
red_alert = BL_UI_Widget(asset_x, asset_y, self.button_size, self.button_size)
red_alert.bg_color = (1.0, 0.0, 0.0, 0.0)
red_alert.visible = False
new_button.red_alert = red_alert
self.red_alerts.append(red_alert)
# if result['downloaded'] > 0:
# ui_bgl.draw_rect(x, y, int(ui_props.thumb_size * result['downloaded'] / 100.0), 2, green)
@ -506,6 +538,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.asset_buttons = []
self.validation_icons = []
self.progress_bars = []
self.red_alerts = []
self.widgets_panel = []
self.panel = BL_UI_Drag_Panel(0, 0, self.bar_width, self.bar_height)
@ -608,6 +641,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
button.visible = False
button.validation_icon.visible = False
button.progress_bar.visible = False
if utils.profile_is_validator():
button.red_alert.set_location(asset_x, asset_y)
i += 1
for a in range(i, len(self.asset_buttons)):
@ -641,6 +676,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
widgets_panel.extend(self.asset_buttons)
widgets_panel.extend(self.validation_icons)
widgets_panel.extend(self.progress_bars)
widgets_panel.extend(self.red_alerts)
widgets = [self.panel]
@ -702,6 +738,14 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
# to hide arrows accordingly
self.scroll_update()
self.window = context.window
self.area = context.area
self.scene = bpy.context.scene
# global active_window_pointer, active_area_pointer, active_region_pointer
# ui.active_window_pointer = self.window.as_pointer()
# ui.active_area_pointer = self.area.as_pointer()
# ui.active_region_pointer = self.region.as_pointer()
return True
def on_finish(self, context):
@ -754,7 +798,8 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
get_tooltip_data(asset_data)
self.asset_name.text = asset_data['name']
self.authors_name.text = asset_data['tooltip_data']['author_text']
self.quality_label.text = asset_data['tooltip_data']['quality']
# print(asset_data['tooltip_data']['quality'])
gimg = asset_data['tooltip_data']['gimg']
if gimg is not None:
gimg = bpy.data.images[gimg]
@ -817,11 +862,21 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
def update_validation_icon(self, asset_button, asset_data):
if utils.profile_is_validator():
ar = bpy.context.window_manager.get('asset ratings')
rating = ar.get(asset_data['id'])
if rating is not None:
rating = rating.to_dict()
v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')]
if v_icon is not None:
img_fp = paths.get_addon_thumbnail_path(v_icon)
asset_button.validation_icon.set_image(img_fp)
asset_button.validation_icon.visible = True
elif rating in (None, {}):
v_icon = 'star_grey.png'
img_fp = paths.get_addon_thumbnail_path(v_icon)
asset_button.validation_icon.set_image(img_fp)
asset_button.validation_icon.visible = True
else:
asset_button.validation_icon.visible = False
else:
@ -854,8 +909,21 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
asset_button.set_image(img_filepath)
self.update_validation_icon(asset_button, asset_data)
if utils.profile_is_validator() and asset_data['verificationStatus'] == 'uploaded':
over_limit = utils.is_upload_old(asset_data)
if over_limit:
redness = min(over_limit * .05, 0.7)
asset_button.red_alert.bg_color = (1, 0, 0, redness)
asset_button.red_alert.visible = True
else:
asset_button.red_alert.visible = False
elif utils.profile_is_validator():
asset_button.red_alert.visible = False
else:
asset_button.validation_icon.visible = False
if utils.profile_is_validator():
asset_button.red_alert.visible = False
def scroll_update(self):
sr = bpy.context.window_manager.get('search results')
@ -869,7 +937,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
self.scroll_offset = min(self.scroll_offset, len(sr) - (self.wcount * self.hcount))
self.scroll_offset = max(self.scroll_offset, 0)
self.update_images()
# print(sro)
if sro['count'] > len(sr) and len(sr) - self.scroll_offset < (self.wcount * self.hcount) + 15:
self.search_more()
@ -898,6 +966,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
def handle_key_input(self, event):
if event.type == 'A':
self.search_by_author(self.active_index)
return True
return False
def scroll_up(self, widget):

View File

@ -17,7 +17,7 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import tasks_queue, utils, paths, search, categories, oauth, ui, ui_panels, colors
from blenderkit import tasks_queue, utils, paths, search, categories, oauth, ui, ui_panels, colors, reports
import bpy
@ -25,6 +25,8 @@ import threading
import requests
import time
import logging
bk_logger = logging.getLogger('blenderkit')
from bpy.props import (
@ -52,7 +54,7 @@ def login(signup, url, r_url, authenticator):
try:
auth_token, refresh_token, oauth_response = authenticator.get_new_token(register=signup, redirect_url=r_url)
except Exception as e:
tasks_queue.add_task((ui.add_report, (e, 20, colors.RED)))
tasks_queue.add_task((reports.add_report, (e, 20, colors.RED)))
bk_logger.debug('tokens retrieved')
tasks_queue.add_task((write_tokens, (auth_token, refresh_token, oauth_response)))
@ -66,7 +68,7 @@ def refresh_token_thread():
thread = threading.Thread(target=refresh_token, args=([preferences.api_key_refresh, url]), daemon=True)
thread.start()
else:
ui.add_report('Already Refreshing token, will be ready soon. If this fails, please login again in Login panel.')
reports.add_report('Already Refreshing token, will be ready soon. If this fails, please login again in Login panel.')
def refresh_token(api_key_refresh, url):
@ -89,7 +91,7 @@ def write_tokens(auth_token, refresh_token, oauth_response):
props = utils.get_search_props()
if props is not None:
props.report = ''
ui.add_report('BlenderKit Re-Login success')
reports.add_report('BlenderKit Re-Login success')
search.get_profile()
categories.fetch_categories_thread(auth_token, force = False)

View File

@ -35,6 +35,10 @@ class BL_UI_OT_draw_operator(Operator):
self.register_handlers(args, context)
context.window_manager.modal_handler_add(self)
self.active_window_pointer = context.window.as_pointer()
self.active_area_pointer = context.area.as_pointer()
self.active_region_pointer = context.region.as_pointer()
return {"RUNNING_MODAL"}
def register_handlers(self, args, context):
@ -80,8 +84,9 @@ class BL_UI_OT_draw_operator(Operator):
# Draw handler to paint onto the screen
def draw_callback_px(self, op, context):
try:
for widget in self.widgets:
widget.draw()
if context.area.as_pointer() == self.active_area_pointer:
for widget in self.widgets:
widget.draw()
except:
pass;
# context.window_manager.event_timer_remove(self.draw_event)

View File

@ -125,7 +125,7 @@ class BL_UI_Widget:
return False
elif event.value == 'PRESS' and (event.ascii != '' or event.type in self.get_input_keys()):
elif event.value == 'PRESS' and self.__inrect and (event.ascii != '' or event.type in self.get_input_keys()):
return self.text_input(event)

View File

@ -17,7 +17,7 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, utils, tasks_queue, rerequests, ui, colors
from blenderkit import paths, utils, tasks_queue, rerequests, ui, colors, reports
import requests
import json
@ -261,7 +261,7 @@ def fetch_categories(API_key, force=False):
tasks_queue.add_task((load_categories, ()))
except Exception as e:
t = 'BlenderKit failed to download fresh categories from the server'
tasks_queue.add_task((ui.add_report,(t, 15, colors.RED)))
tasks_queue.add_task((reports.add_report(),(t, 15, colors.RED)))
bk_logger.debug(t)
bk_logger.exception(e)
if not os.path.exists(categories_filepath):

View File

@ -106,11 +106,6 @@ def get_comments_local(asset_id):
return comments
return None
def get_comments_thread(asset_id, api_key):
thread = threading.Thread(target=get_comments, args=([asset_id, api_key]), daemon=True)
thread.start()
def get_comments_thread(asset_id, api_key):
thread = threading.Thread(target=get_comments, args=([asset_id, api_key]), daemon=True)
thread.start()
@ -166,9 +161,9 @@ def count_all_notifications():
def check_notifications_read():
'''checks if all notifications were already read, and removes them if so'''
notifications = bpy.context.window_manager.get('bkit notifications')
if notifications is None:
if notifications is None or notifications.get('count') == 0:
return True
for n in notifications:
for n in notifications['results']:
if n['unread'] == 1:
return False
bpy.context.window_manager['bkit notifications'] = None
@ -198,7 +193,7 @@ def get_notifications(api_key, all_count = 1000):
r = rerequests.get(url, params=params, verify=True, headers=headers)
if r.status_code ==200:
rj = r.json()
print(rj)
# print(rj)
# no new notifications?
if all_count >= rj['allCount']:
tasks_queue.add_task((store_notifications_count_local, ([rj['allCount']])))

View File

@ -779,7 +779,7 @@ class Downloader(threading.Thread):
if not has_url:
tasks_queue.add_task(
(ui.add_report, ('Failed to obtain download URL for %s.' % asset_data['name'], 5, colors.RED)))
(reports.add_report, ('Failed to obtain download URL for %s.' % asset_data['name'], 5, colors.RED)))
return;
if tcom.error:
return
@ -814,7 +814,7 @@ class Downloader(threading.Thread):
else:
# bk_logger.debug(total_length)
if int(total_length) < 1000: # means probably no file returned.
tasks_queue.add_task((ui.add_report, (response.content, 20, colors.RED)))
tasks_queue.add_task((reports.add_report, (response.content, 20, colors.RED)))
tcom.report = response.content
@ -875,7 +875,7 @@ def download(asset_data, **kwargs):
sprops = utils.get_search_props()
report = f"Maximum retries exceeded for {asset_data['name']}"
sprops.report = report
ui.add_report(report, 5, colors.RED)
reports.add_report(report, 5, colors.RED)
bk_logger.debug(sprops.report)
return
@ -1122,12 +1122,12 @@ def get_download_url(asset_data, scene_id, api_key, tcom=None, resolution='blend
return True
# let's print it into UI
tasks_queue.add_task((ui.add_report, (str(r), 10, colors.RED)))
tasks_queue.add_task((reports.add_report, (str(r), 10, colors.RED)))
if r.status_code == 403:
report = 'You need Full plan to get this item.'
# r1 = 'All materials and brushes are available for free. Only users registered to Standard plan can use all models.'
# tasks_queue.add_task((ui.add_report, (r1, 5, colors.RED)))
# tasks_queue.add_task((reports.add_report, (r1, 5, colors.RED)))
if tcom is not None:
tcom.report = report
tcom.error = True

View File

@ -129,12 +129,12 @@ def get_temp_dir(subdir=None):
cleanup_old_folders()
except:
tasks_queue.add_task((ui.add_report, ('Cache directory not found. Resetting Cache folder path.',)))
tasks_queue.add_task((reports.add_report, ('Cache directory not found. Resetting Cache folder path.',)))
p = default_global_dict()
if p == user_preferences.global_dir:
message = 'Global dir was already default, plese set a global directory in addon preferences to a dir where you have write permissions.'
tasks_queue.add_task((ui.add_report, (message,)))
tasks_queue.add_task((reports.add_report, (message,)))
return None
user_preferences.global_dir = p
tempdir = get_temp_dir(subdir=subdir)

View File

@ -78,7 +78,7 @@ def store_rating_local(asset_id, type='quality', value=0):
context = bpy.context
ar = context.window_manager['asset ratings']
ar[asset_id] = ar.get(asset_id, {})
ar[asset_id]['type'] = value
ar[asset_id][type] = value
def get_rating(asset_id, headers):
@ -101,6 +101,7 @@ def get_rating(asset_id, headers):
if r.status_code == 200:
rj = r.json()
ratings = {}
print(rj)
# store ratings - send them to task queue
for r in rj['results']:
ratings[r['ratingType']] = r['score']
@ -353,6 +354,8 @@ class RatingsProperties():
def prefill_ratings(self):
# pre-fill ratings
ratings = get_rating_local(self.asset_id)
print('prefill ratings')
print(ratings)
if ratings and ratings.get('quality'):
self.rating_quality = ratings['quality']
if ratings and ratings.get('working_hours'):

View File

@ -17,7 +17,11 @@
# ##### END GPL LICENSE BLOCK #####
import time
from blenderkit import colors
import bpy
from blenderkit import colors, asset_bar_op, ui_bgl, utils
reports = []
def add_report(text='', timeout=5, color=colors.GREEN):
global reports
@ -26,18 +30,24 @@ def add_report(text='', timeout=5, color=colors.GREEN):
if old_report.text == text:
old_report.timeout = old_report.age + timeout
return
report = Report(text=text, timeout=timeout, color=color)
report = Report(text=text, timeout=timeout, color=color)
reports.append(report)
class Report():
def __init__(self, text='', timeout=5, color=(.5, 1, .5, 1)):
def __init__(self, area_pointer=0, text='', timeout=5, color=(.5, 1, .5, 1)):
self.text = text
self.timeout = timeout
self.start_time = time.time()
self.color = color
self.draw_color = color
self.age = 0
if asset_bar_op.active_area_pointer == 0:
w, a, r = utils.get_largest_area(area_type='VIEW_3D')
self.active_area_pointer = a.as_pointer()
else:
self.active_area_pointer = asset_bar_op.active_area_pointer
def fade(self):
fade_time = 1
@ -53,5 +63,5 @@ class Report():
pass;
def draw(self, x, y):
if bpy.context.area.as_pointer() == active_area_pointer:
ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
if (bpy.context.area is not None and bpy.context.area.as_pointer() == self.active_area_pointer):
ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)

View File

@ -44,7 +44,7 @@ def rerequest(method, url, recursion=0, **kwargs):
response = requests.request(method, url, **kwargs)
except Exception as e:
print(e)
tasks_queue.add_task((ui.add_report, (
tasks_queue.add_task((reports.add_report, (
'Connection error.', 10)))
return FakeResponse()
@ -57,13 +57,13 @@ def rerequest(method, url, recursion=0, **kwargs):
except:
rdata = {}
tasks_queue.add_task((ui.add_report, (method + ' request Failed.' + str(rdata.get('detail')),)))
tasks_queue.add_task((reports.add_report, (method + ' request Failed.' + str(rdata.get('detail')),)))
if rdata.get('detail') == 'Invalid token.':
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
if user_preferences.api_key != '':
if user_preferences.enable_oauth and user_preferences.api_key_refresh != '':
tasks_queue.add_task((ui.add_report, (
tasks_queue.add_task((reports.add_report, (
'refreshing token. If this fails, please login in BlenderKit Login panel.', 10)))
refresh_url = paths.get_bkit_url()
auth_token, refresh_token, oauth_response = bkit_oauth.refresh_token(
@ -84,11 +84,11 @@ def rerequest(method, url, recursion=0, **kwargs):
bk_logger.debug('reresult', response.status_code)
if response.status_code >= 400:
bk_logger.debug('reresult', response.text)
tasks_queue.add_task((ui.add_report, (
tasks_queue.add_task((reports.add_report, (
response.text, 10)))
else:
tasks_queue.add_task((ui.add_report, (
tasks_queue.add_task((reports.add_report, (
'Refreshing token failed.Please login manually.', 10)))
# tasks_queue.add_task((bkit_oauth.write_tokens, ('', '', '')))
tasks_queue.add_task((bpy.ops.wm.blenderkit_login, ('INVOKE_DEFAULT',)), fake_context=True)

View File

@ -17,7 +17,7 @@
# ##### END GPL LICENSE BLOCK #####
from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests, \
resolutions, image_utils, ratings_utils, comments_utils
resolutions, image_utils, ratings_utils, comments_utils, reports
import blenderkit
from bpy.app.handlers import persistent
@ -83,15 +83,22 @@ thumb_full_download_threads = queue.Queue()
reports_queue = queue.Queue()
all_thumbs_loaded = True
rtips = ['Click or drag model or material in scene to link/append ',
"Please rate responsively and plentifully. This helps us distribute rewards to the authors.",
"Click on brushes to link them into scene.",
"All materials are free.",
"Storage for public assets is unlimited.",
"Locked models are available if you subscribe to Full plan.",
"Login to upload your own models, materials or brushes.",
"Use 'A' key over asset bar to search assets by same author.",
"Use 'W' key over asset bar to open Authors webpage.", ]
rtips_string = """You can disable tips in the add-on preferences.
Ratings help us distribute funds to creators.
Creators also gain credits for free assets from subscribers.
Click or drag model or material in scene to link/append
Right click in the asset bar for more options
Use Append in import settings if you want to edit downloaded objects.
Please rate responsively and plentifully. This helps us distribute rewards to the authors.
All materials are free.
Storage for public assets is unlimited.
Locked models are available if you subscribe to Full plan.
Login to upload your own models, materials or brushes.
Use 'A' key over the asset bar to search assets by the same author.
Use semicolon - ; to hide or show the AssetBar.
Support the authors by subscribing to Full plan.
"""
rtips = rtips_string.splitlines()
def refresh_token_timer():
@ -192,7 +199,7 @@ def scene_load(context):
bpy.app.timers.register(refresh_token_timer, persistent=True, first_interval=36000)
if utils.experimental_enabled() and not bpy.app.timers.is_registered(
refresh_notifications_timer) and not bpy.app.background:
bpy.app.timers.register(refresh_notifications_timer, persistent=True, first_interval=2)
bpy.app.timers.register(refresh_notifications_timer, persistent=True, first_interval=5)
update_assets_data()
@ -211,7 +218,8 @@ def fetch_server_data():
get_profile()
if bpy.context.window_manager.get('bkit_categories') is None:
categories.fetch_categories_thread(api_key, force=False)
all_notifications_count = comments_utils.count_all_notifications()
comments_utils.get_notifications_thread(api_key, all_count = all_notifications_count)
first_time = True
last_clipboard = ''
@ -374,7 +382,7 @@ def search_timer():
if preferences.tips_on_start:
utils.get_largest_area()
ui.update_ui_size(ui.active_area_pointer, ui.active_region_pointer)
ui.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN)
reports.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN)
# utils.p('end search timer')
return 3.0
@ -481,7 +489,7 @@ def search_timer():
props.search_error = False
props.report = 'Found %i results. ' % (wm['search results orig']['count'])
if len(wm['search results']) == 0:
tasks_queue.add_task((ui.add_report, ('No matching results found.',)))
tasks_queue.add_task((reports.add_report, ('No matching results found.',)))
# undo push
# bpy.ops.wm.undo_push_context(message='Get BlenderKit search')
# show asset bar automatically, but only on first page - others are loaded also when asset bar is hidden.
@ -740,7 +748,7 @@ def fetch_gravatar(adata = None):
'''
# utils.p('fetch gravatar')
print(adata)
# print(adata)
# fetch new avatars if available already
if adata.get('avatar128') is not None:
avatar_path = paths.get_temp_dir(subdir='bkit_g/') + adata['id'] + '.jpg'
@ -968,13 +976,13 @@ class Searcher(threading.Thread):
if hasattr(r, 'text'):
error_description = parse_html_formated_error(r.text)
reports_queue.put(error_description)
tasks_queue.add_task((ui.add_report, (error_description, 10, colors.RED)))
tasks_queue.add_task((reports.add_report, (error_description, 10, colors.RED)))
bk_logger.error(e)
return
mt('data parsed ')
if not rdata.get('results'):
utils.pprint(rdata)
# utils.pprint(rdata)
# if the result was converted to json and didn't return results,
# it means it's a server error that has a clear message.
# That's why it gets processed in the update timer, where it can be passed in messages to user.
@ -1437,7 +1445,7 @@ def search(category='', get_next=False, author_id=''):
if orig_results is not None and get_next:
params['next'] = orig_results['next']
add_search_process(query, params)
tasks_queue.add_task((ui.add_report, ('BlenderKit searching....', 2)))
tasks_queue.add_task((reports.add_report, ('BlenderKit searching....', 2)))
props.report = 'BlenderKit searching....'
@ -1583,7 +1591,7 @@ class SearchOperator(Operator):
sprops.search_keywords = self.keywords
search(category=self.category, get_next=self.get_next, author_id=self.author_id)
# bpy.ops.view3d.blenderkit_asset_bar()
# bpy.ops.view3d.blenderkit_asset_bar_widget()
return {'FINISHED'}

View File

@ -18,7 +18,7 @@
from blenderkit import paths, ratings, utils, search, upload, ui_bgl, download, bg_blender, colors, tasks_queue, \
ui_panels, icons, ratings_utils
ui_panels, icons, ratings_utils, reports
import bpy
@ -51,8 +51,6 @@ active_area_pointer = None
active_window_pointer = None
active_region_pointer = None
reports = []
mappingdict = {
'MODEL': 'model',
'SCENE': 'scene',
@ -101,43 +99,6 @@ def get_approximate_text_width(st):
return size # Convert to picas
def add_report(text='', timeout=5, color=colors.GREEN):
global reports
# check for same reports and just make them longer by the timeout.
for old_report in reports:
if old_report.text == text:
old_report.timeout = old_report.age + timeout
return
report = Report(text=text, timeout=timeout, color=color)
reports.append(report)
class Report():
def __init__(self, text='', timeout=5, color=(.5, 1, .5, 1)):
self.text = text
self.timeout = timeout
self.start_time = time.time()
self.color = color
self.draw_color = color
self.age = 0
def fade(self):
fade_time = 1
self.age = time.time() - self.start_time
if self.age + fade_time > self.timeout:
alpha_multiplier = (self.timeout - self.age) / fade_time
self.draw_color = (self.color[0], self.color[1], self.color[2], self.color[3] * alpha_multiplier)
if self.age > self.timeout:
global reports
try:
reports.remove(self)
except Exception as e:
pass;
def draw(self, x, y):
if bpy.context.area.as_pointer() == active_area_pointer:
ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
def get_asset_under_mouse(mousex, mousey):
s = bpy.context.scene
@ -528,8 +489,8 @@ def draw_callback_2d_progress(self, context):
draw_progress(x, y - index * 30, '%s' % n + tcom.lasttext,
tcom.progress)
index += 1
global reports
for report in reports:
for report in reports.reports:
# print('drawing reports', x, y, report.text)
report.draw(x, y - index * 30)
index += 1
report.fade()
@ -557,21 +518,6 @@ def draw_callback_2d_upload_preview(self, context):
draw_tooltip(ui_props.bar_x, ui_props.bar_y, name=props.name, img=img)
def is_upload_old(asset_data):
'''
estimates if the asset is far too long in the 'uploaded' state
This returns the number of days the validation is over the limit.
'''
date_time_str = asset_data["created"][:10]
# date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d')
today = date_time_obj.today()
age = today - date_time_obj
old = datetime.timedelta(days=7)
if age > old:
return (age.days - old.days)
return 0
def get_large_thumbnail_image(asset_data):
'''Get thumbnail image from asset data'''
@ -705,7 +651,7 @@ def draw_asset_bar(self, context):
# code to inform validators that the validation is waiting too long and should be done asap
if result['verificationStatus'] == 'uploaded':
if is_validator:
over_limit = is_upload_old(result)
over_limit = utils.is_upload_old(result)
if over_limit:
redness = min(over_limit * .05, 0.5)
red = (1, 0, 0, redness)
@ -1903,7 +1849,7 @@ class RunAssetBarWithContext(bpy.types.Operator):
C_dict = utils.get_fake_context(context)
if C_dict.get('window'): # no 3d view, no asset bar.
preferences = bpy.context.preferences.addons['blenderkit'].preferences
if 1:# preferences.experimental_features:
if 1:#preferences.experimental_features:
bpy.ops.view3d.blenderkit_asset_bar_widget(C_dict, 'INVOKE_REGION_WIN', keep_running=self.keep_running,
do_search=self.do_search)

View File

@ -334,7 +334,7 @@ def draw_assetbar_show_hide(layout, props):
ttip = 'Click to Show Asset Bar'
preferences = bpy.context.preferences.addons['blenderkit'].preferences
if 1: # preferences.experimental_features:
if 1:#preferences.experimental_features:
op = layout.operator('view3d.blenderkit_asset_bar_widget', text='', icon=icon)
else:
op = layout.operator('view3d.blenderkit_asset_bar', text='', icon=icon)
@ -609,7 +609,7 @@ class MarkNotificationRead(bpy.types.Operator):
def execute(self, context):
notifications = bpy.context.window_manager['bkit notifications']
for n in notifications:
for n in notifications['results']:
if n['id'] == self.notification_id:
n['unread'] = 0
comments_utils.check_notifications_read()
@ -634,7 +634,7 @@ class MarkAllNotificationsRead(bpy.types.Operator):
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
notifications = bpy.context.window_manager['bkit notifications']
for n in notifications:
for n in notifications.get('results'):
if n['unread'] == 1:
n['unread'] = 0
comments_utils.mark_notification_read_thread(api_key, n['id'])
@ -705,42 +705,54 @@ def draw_notification(self, notification, width=600):
box = layout.box()
firstline = f"{notification['actor']['string']} {notification['verb']} {notification['target']['string']}"
box1 = box.box()
row = box1.row()
utils.label_multiline(row, text=firstline, width=width)
# row = box1.row()
op = row.operator("wm.blenderkit_mark_notification_read", text="", icon='CANCEL')
op.notification_id = notification['id']
split_last = 0.7
if notification['description']:
rows = utils.label_multiline(box, text=notification['description'], width=width)
split = rows[-1].split(factor=0.8)
split_last = 0
rows = utils.label_multiline(box1, text=firstline, width=width, split_last = split_last)
if notification['description']:
rows = utils.label_multiline(box, text=notification['description'], width=width, split_last = 0.7)
else:
row = layout.row()
split = row.split(factor=0.8)
split.label(text='')
split = split.split()
if notification['target']:
# row = layout.row()
# split = row.split(factor=.8)
# split.label(text='')
# split = split.split()
op = split.operator('wm.blenderkit_open_notification_target', text='Open page', icon='GREASEPENCIL')
# split = rows[-1].split(factor=0.8)
# split = split.split()
# split.alignment = 'RIGHT'
# row = split.row(align = True)
row = rows[-1]
row = row.row(align=False)
# row = row.split(factor = 0.7)
op = row.operator('wm.blenderkit_open_notification_target', text='Open page', icon='HIDE_OFF')
op.tooltip = 'Open the browser on the asset page to comment'
op.url = paths.get_bkit_url() + notification['target']['url']
op.notification_id = notification['id']
# split =
op = row.operator("wm.blenderkit_mark_notification_read", text="", icon='CANCEL')
op.notification_id = notification['id']
def draw_notifications(self, context, width=600):
layout = self.layout
notifications = bpy.context.window_manager.get('bkit notifications')
if notifications is not None:
if notifications is not None and notifications.get('count')>0:
row = layout.row()
# row.alert = True
split = row.split(factor = 0.7)
split.label(text='')
split = split.split()
split.operator('wm.blenderkit_mark_notifications_read_all', text = 'Mark All Read', icon = 'CANCEL')
for notification in notifications:
for notification in notifications['results']:
if notification['unread'] == 1:
draw_notification(self, notification, width=width)
@ -778,7 +790,7 @@ class VIEW3D_PT_blenderkit_notifications(Panel):
@classmethod
def poll(cls, context):
notifications = bpy.context.window_manager.get('bkit notifications')
if notifications is not None and len(notifications) > 0:
if notifications is not None and len(notifications['results']) > 0:
return True
return False
@ -1554,8 +1566,7 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False):
if asset_data['assetType'] == 'model':
op = layout.operator('object.blenderkit_regenerate_thumbnail', text='Regenerate thumbnail')
op.asset_index = ui_props.active_index
if asset_data['assetType'] == 'material':
elif asset_data['assetType'] == 'material':
op = layout.operator('object.blenderkit_regenerate_material_thumbnail', text='Regenerate thumbnail')
op.asset_index = ui_props.active_index
# op.asset_id = asset_data['id']
@ -2129,7 +2140,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
# name_row = name_row.row()
for i, c in enumerate(cat_path):
cat_name = cat_path_names[i]
op = name_row.operator('view3d.blenderkit_asset_bar', text=cat_name + ' >', emboss=True)
op = name_row.operator('view3d.blenderkit_asset_bar_widget', text=cat_name + ' >', emboss=True)
op.do_search = True
op.keep_running = True
op.tooltip = f"Browse {cat_name} category"
@ -2225,13 +2236,30 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
tip_box = layout.box()
tip_box.label(text=self.tip)
# comments
if utils.profile_is_validator() and bpy.app.debug_value == 2:
if utils.profile_is_validator():
comments = bpy.context.window_manager.get('asset comments', {})
self.comments = comments.get(self.asset_data['assetBaseId'], [])
if self.comments is not None:
for comment in self.comments:
self.draw_comment(context, layout, comment, width=self.width)
def prefill_ratings(self):
# pre-fill ratings
ratings = ratings_utils.get_rating_local(self.asset_id)
print('prefill ratings')
print(ratings)
if ratings and ratings.get('quality'):
self.rating_quality = ratings['quality']
if ratings and ratings.get('working_hours'):
wh = int(ratings['working_hours'])
whs = str(wh)
if wh in self.possible_wh_values:
self.rating_work_hours_ui = whs
if wh < 6 and wh in self.possible_wh_values_1_5:
self.rating_work_hours_ui_1_5 = whs
if wh < 11 and wh in self.possible_wh_values_1_10:
self.rating_work_hours_ui_1_10 = whs
def execute(self, context):
wm = context.window_manager
ui_props = context.window_manager.blenderkitUI
@ -2262,7 +2290,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
self.prefill_ratings()
# get comments
if utils.profile_is_validator() and bpy.app.debug_value == 2:
if utils.profile_is_validator():
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
api_key = user_preferences.api_key
comments = comments_utils.get_comments_local(asset_data['assetBaseId'])
@ -2452,7 +2480,7 @@ def draw_panel_categories(self, context):
cats = categories.get_category(wm['bkit_categories'], cat_path=acat)
# draw freebies only in models parent category
# if ui_props.asset_type == 'MODEL' and len(acat) == 1:
# op = col.operator('view3d.blenderkit_asset_bar', text='freebies')
# op = col.operator('view3d.blenderkit_asset_bar_widget', text='freebies')
# op.free_only = True
for c in cats['children']:
@ -2465,7 +2493,7 @@ def draw_panel_categories(self, context):
ctext = '%s (%i)' % (c['name'], c['assetCount'])
preferences = bpy.context.preferences.addons['blenderkit'].preferences
if 1: # preferences.experimental_features:
if 1:#preferences.experimental_features:
op = row.operator('view3d.blenderkit_asset_bar_widget', text=ctext)
else:
op = row.operator('view3d.blenderkit_asset_bar', text=ctext)
@ -2570,7 +2598,7 @@ def header_search_draw(self, context):
layout.popover(panel="VIEW3D_PT_blenderkit_advanced_HDR_search", text="", icon_value=icon_id)
notifications = bpy.context.window_manager.get('bkit notifications')
if notifications is not None and len(notifications) > 0:
if notifications is not None and notifications['count'] > 0:
layout.operator('wm.show_notifications', text="", icon_value=pcoll['bell'].icon_id)
# layout.popover(panel="VIEW3D_PT_blenderkit_notifications", text="", icon_value=pcoll['bell'].icon_id)

View File

@ -18,7 +18,7 @@
from blenderkit import asset_inspector, paths, utils, bg_blender, autothumb, version_checker, search, ui_panels, ui, \
overrides, colors, rerequests, categories, upload_bg, tasks_queue, image_utils, asset_bar_op
overrides, colors, rerequests, categories, upload_bg, tasks_queue, image_utils, asset_bar_op, reports
import tempfile, os, subprocess, json, re
@ -715,7 +715,7 @@ class FastMetadata(bpy.types.Operator):
thread = threading.Thread(target=patch_individual_metadata,
args=(self.asset_id, mdict, user_preferences.api_key))
thread.start()
tasks_queue.add_task((ui.add_report, (f'Uploading metadata for {self.name}. '
tasks_queue.add_task((reports.add_report, (f'Uploading metadata for {self.name}. '
f'Refresh search results to see that changes applied correctly.', 8,)))
return {'FINISHED'}
@ -871,7 +871,7 @@ class Uploader(threading.Thread):
message = str(message).replace("'", "")
# this adds a UI report but also writes above the upload panel fields.
tasks_queue.add_task((ui.add_report, (message,)))
tasks_queue.add_task((reports.add_report, (message,)))
estring = f"{self.export_data['eval_path_state']} = '{message}'"
tasks_queue.add_task((exec, (estring,)))
@ -895,14 +895,14 @@ class Uploader(threading.Thread):
# self.upload_data['license'] = 'ovejajojo'
json_metadata = self.upload_data # json.dumps(self.upload_data, ensure_ascii=False).encode('utf8')
# tasks_queue.add_task((ui.add_report, ('Posting metadata',)))
# tasks_queue.add_task((reports.add_report, ('Posting metadata',)))
self.send_message('Posting metadata')
if self.export_data['assetBaseId'] == '':
try:
r = rerequests.post(url, json=json_metadata, headers=headers, verify=True,
immediate=True) # files = files,
# tasks_queue.add_task((ui.add_report, ('uploaded metadata',)))
# tasks_queue.add_task((reports.add_report, ('uploaded metadata',)))
utils.p(r.text)
self.send_message('uploaded metadata')
@ -920,7 +920,7 @@ class Uploader(threading.Thread):
immediate=True) # files = files,
self.send_message('uploaded metadata')
# tasks_queue.add_task((ui.add_report, ('uploaded metadata',)))
# tasks_queue.add_task((reports.add_report, ('uploaded metadata',)))
# parse the request
# print('uploaded metadata')
print(r.text)
@ -938,10 +938,10 @@ class Uploader(threading.Thread):
return {'FINISHED'}
try:
rj = r.json()
utils.pprint(rj)
# utils.pprint(rj)
# if r.status_code not in (200, 201):
# if r.status_code == 401:
# ###ui.add_report(r.detail, 5, colors.RED)
# ###reports.add_report(r.detail, 5, colors.RED)
# return {'CANCELLED'}
# if props.asset_base_id == '':
# props.asset_base_id = rj['assetBaseId']

View File

@ -50,7 +50,7 @@ class upload_in_chunks(object):
break
self.readsofar += len(data)
percent = self.readsofar * 1e2 / self.totalsize
tasks_queue.add_task((ui.add_report, (f"Uploading {self.report_name} {percent}%",)))
tasks_queue.add_task((reports.add_report, (f"Uploading {self.report_name} {percent}%",)))
# bg_blender.progress('uploading %s' % self.report_name, percent)
# sys.stderr.write("\r{percent:3.0f}%".format(percent=percent))
@ -65,7 +65,7 @@ def upload_file(upload_data, f):
version_id = upload_data['id']
message = f"uploading {f['type']} {os.path.basename(f['file_path'])}"
tasks_queue.add_task((ui.add_report, (message,)))
tasks_queue.add_task((reports.add_report, (message,)))
upload_info = {
'assetId': version_id,
@ -95,17 +95,17 @@ def upload_file(upload_data, f):
upload_response = rerequests.post(upload_done_url, headers=headers, verify=True)
# print(upload_response)
# print(upload_response.text)
tasks_queue.add_task((ui.add_report, (f"Finished file upload: {os.path.basename(f['file_path'])}",)))
tasks_queue.add_task((reports.add_report, (f"Finished file upload: {os.path.basename(f['file_path'])}",)))
return True
else:
print(upload_response.text)
message = f"Upload failed, retry. File : {f['type']} {os.path.basename(f['file_path'])}"
tasks_queue.add_task((ui.add_report, (message,)))
tasks_queue.add_task((reports.add_report, (message,)))
except Exception as e:
print(e)
message = f"Upload failed, retry. File : {f['type']} {os.path.basename(f['file_path'])}"
tasks_queue.add_task((ui.add_report, (message,)))
tasks_queue.add_task((reports.add_report, (message,)))
time.sleep(1)
# confirm single file upload to bkit server
@ -123,7 +123,7 @@ def upload_files(upload_data, files):
uploaded = upload_file(upload_data, f)
if not uploaded:
uploaded_all = False
tasks_queue.add_task((ui.add_report, (f"Uploaded all files for asset {upload_data['name']}",)))
tasks_queue.add_task((reports.add_report, (f"Uploaded all files for asset {upload_data['name']}",)))
return uploaded_all

View File

@ -28,6 +28,7 @@ import shutil
import logging
import traceback
import inspect
import datetime
bk_logger = logging.getLogger('blenderkit')
@ -913,7 +914,7 @@ def get_fake_context(context, area_type='VIEW_3D'):
# def is_url(text):
def label_multiline(layout, text='', icon='NONE', width=-1, max_lines=10):
def label_multiline(layout, text='', icon='NONE', width=-1, max_lines=10, split_last = 0):
'''
draw a ui label, but try to split it in multiple lines.
@ -958,6 +959,8 @@ def label_multiline(layout, text='', icon='NONE', width=-1, max_lines=10):
if li > max_lines:
break;
row = layout.row()
if split_last > 0:
row = row.split(factor=split_last)
row.label(text=l, icon=icon)
rows.append(row)
icon = 'NONE'
@ -965,5 +968,20 @@ def label_multiline(layout, text='', icon='NONE', width=-1, max_lines=10):
return rows
def is_upload_old(asset_data):
'''
estimates if the asset is far too long in the 'uploaded' state
This returns the number of days the validation is over the limit.
'''
date_time_str = asset_data["created"][:10]
# date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d')
today = date_time_obj.today()
age = today - date_time_obj
old = datetime.timedelta(days=5)
if age > old:
return (age.days - old.days)
return 0
def trace():
traceback.print_stack()