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 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RWI14-?iy0WWg+Z8+Vb&Z8 z1_lPR64!_lx6GVW1~;piq@2{el++>@*Dx3}qckZdy(qCDBQv=e&M?vgEBq<F8)Ui! z$OM<fl2isG10zFy0~38C3k5?<D^mk26I0#A@iQ107z99SLNaqx84L`ppvdseVYNdb zXMyD0iW0$+h6e*hl^7Tp1VMt%iMgpoi3|`*&%nS?=w4(!0|Ns$NHRD%H7}JRGdVx6 z`1|LxW(*7r!XV)wun!Pc=^2{rb6hRSz`(!*lJ-r_%`d8CFx0cK^tIo;@dyJ0gB(aa zxiUE?wO9|L+b6LywMefxF}ENmwV1)s$ilKLs*QnxK@Eq>lKhgyocI#B_w)=+vNrYw zGcYiy<IqtAb|s1yW6qtEau^sG3~*>EuFOl$D9X>vtV%4&%+EvCYhYmP;Z?@K!1>zK z#WAFU@$H=I6*B3P?XwFw6m=C_lvHJ$I2FJ22Q-;?d2s|abxny1=$;~!6}?vck8?{{ zpdhExQW1U8RZ6-}qDQ$GbwnNAmKks`$m?iCaJ5txM^sji=`jiant5mU6qc2jm7P0R zw|n#M&xhWhIa6M_|NY;+_iNM6t4`l?$>;($D8P1ZzWx4vUZJa&NiSKQ_s_9vb=&&Y zvkIn^%q$F_P%fFnG^vVDhf&od?rGeU`FqWEj@Udi)%o=N^V83>kN-Jjqqxw1o^$w; zPNTi^SdSn0So->!Yt0ks+om&_72*%&vz9mHD)gL}mRurvEjwk2`m067O8Is=B}cAW z9DTm0h1uCXM%nkD<)weECw?CYFFdr;<HnEGy7G6J{_&;7{%d7?&ng`xu>N}R39%{r z*mL|A)*P}j54yLz(X2N%$TC6O^se2vTYmlzja<)9VJ^w>nR0n?ZjEZT|D<EHTr$(j z->X>IS*fkpl=NRIviy>D<tyRoGrCq(X8nEGq!ObV=+ztilGjMBcB`XmW=O5D>g(9) zu|g6mQ3ZF3Z-)vVUYzvPB=68OU$cY0_ttGF7qEA|vu4vm>$&R7HVeCL?6ftqV2ERm zano98vrgP;_hIET*&<bv->V(wH~X42KiYWY@`1Y>%v=|LmO5YAr?k)f+@Jky{Eeb> zHcIi`a6Axxz)nD=Dz^KH_J(OSpXY4r5blrRYrI`>;G)9K*w=Of=L=O=Z0eb`eA6dM z--sifdz18cG*3)FX)Ik{alL8L5ti+f8W?xpV>hc_?EYyocmG<S2FB+nmu|c2-|&*r zwd5JQ>;<bt2ItOZ>pj2S{kIrZvguhZc*{6h{l1WOKEvxt`R`ADU-0K|$r-Qkto3X6 zs;<5I?c9sUTplZD{nWf<$9Sl)`O{Ce9Q%T0^Vtj8e;a(brCp@ZANMrX{DX*s#iOzX zYs?pOu2$LBAm7lu;BxJbwi)(X53WxC(BD{Dc)&B~r-22ZNbVW$8~Hcf8~Ii!t$w&u ztJ636V7l#LjnfC7z3X{->e88rnB#J9^tNhR2fxmp)9f?#@YkwyJfTK2x4mlft=%@O zQqbSP`{a4as?2x$-h{EQal3x;@Zlpo;f-4J(_b`9Z`Yi&u;Tc?b;(Z{OTTN>i%+<o zxXAhZ%nP%H4+jb7-@4wkT6@XWg|5P3t!sLHlJeJGV$NA`J!Ff+xp+Qyo5Rj8cg)k= zJLmOp$=6@L7*r-kG)Z<&7thsv%I+6aaEEPMqgK%oOIP+4Kb|k}5;y+K`yh?0gh_u& z&Zb|=6GNX(bCTudy7Dajw3uAM!wrA<3WN>*8cckB+;@vz@S9t8M;o{nEw^V|r(MkS zv7RY(m+na^-APMzQ$MLG*|V<F+uc9$+Xk+~(fURIV*_rTJu~5C*}Csd+aI(z9=^!+ z@$&`dEsoK@j!fgf=hYIU^IcVgZ^GmE%+DLR`4)&roY;QQz<;*7i~1e@KOEane75>p zp)&8`YO@UQ=?R;6|7AW9zSG(vH1NK8l1H~(!ZYOuukWpKT&J-1-u~%aZ(p7KE_6P- zFvI`bF&nw1YPT*4zKJ=Nvv+pGc_zyhTHF4yH|YPco>cDOBG+}I>pJg}-K;h3T{oq- z&En^u|M1h2@5VYspP~!ccGuMY=6c89@V@c(L9u|)E2R>5<k#-kPYyd#x9)sn;FI|& zRWf&2*0Ee~s&9xCt(Sg~lq|hN@DGoKS=XPLKG(lay7!%1QfTL%8u<?w)@J0qzZ&@E zjoyu|pYsoVJ@Bze?C9Ai{0HSW2;aEvF8Fna*Ds~D`?FJTJZISYn1$20xldwiz^1aW z(4UFtHn<9lTf{TTzf_G^%6VJsbST*3;bfN?KbaFQ1Z~#O_dl>`@_UaDl?DESKZ`<d zF!uy(nzw-Y9n)ey_f=6Yg*P@nt(W?A_l&65O6D_-!a0A<ncG#u8N2VTTDTx2=IxG? zMSQgjdTO@vls0r0G<`lG-QX3^`RWXJ%KYewf1DpE9QnVcYwA=Pfv$ohM&I|%sutg1 zzU5Orn@rI@wmUPz{uO?oC6f?&b3%ZucUh$lM_ioI|9Y)}yX=f#4P({?yTZf17k z?kf=AAzve~!!ORneaE3nrW!^Y9zVx@e36rT-Y|F{<kU-6jdR~4Y~dfn_pEf0U3)H{ zY2Ro5CijD$9p*JEE1x!K7#6oIdCz9``0N9%1pS)~X^}Zs)el~NaQwi@uaX5-Q^frD z=}C4oJ1O^dOv_(?gL95{9Dm8NsRv}YoxbI9JU3aTt$*{C7pfnaEV%x7UR=MYz2NY1 zt>jXNeQ8#Q#U@$0w(rQg#W?L?`+=(qJU@v{Eo3d`@z|Gg`_tdVSc~@KlHxLtqE-Ao z|Fg|fs$Z(K@9yl1^K&X1x2Wy@p?`3?qj1zT;TPKv=s#PvG&x}9g<IR2k003bR8Z@| zh8-16533Ju4*qiB^R>i}GtTQAtBC)6mrJHuUH6<L`+-Y`tQ-z29;jz5P@nN+zxn5_ zj~oqNS5Nu4MYfY+KI6V%Ki&V!8mb$-ABQzDa@?FGX#C1Me-poGX+2*~Z_(}SKE{rV zoaN0ThbQiRZFfO#@pFs!yIU9Z9<WTDBW<ew)4$<tE$cSMzdR08{619Ax!?T1!D>;T zw1f4-Z^5<}EQkKE6qx^zl?eUkaLjhWM(?wo@~%9~_E@(iGxl?YHLxcw?qbiHm$Yj2 z#7hzEDLJNl>$c9lRl+>GAR>JB6C+mN@`lp++3F^D86U5>Z&5PE^ifn{@4A?@$h%f% zyV$RquABB<J4KGYvNyIVtNyZVg6wOng&VyuL>4sgrie*(#`LN9ny<@yJ$;JXZHuWN z4XvdnNSsga;^2B@Yql=fy)@pfGDeRtb??*2Y4^*29X`~_AAWKl_oW4KjU4vTHL?|= zACh(JA4&RgTvFAj&)*g3Z}_+FGY3afb*Z%8PyHs*4;&Rjb7ou-7ZNUHI8x14uUuhW zVcIeI<h^SiS3+FdWm)-~@*lMb2e0+Ldr0x#^mo<=`w!F~T+@+%u8!&3m8S`-ei|l- zf4Dt=SNg9NGj`la_;Yr%bApkl8B4%N-z&c!A28g1&Sln_ABtU1pGg&nZ?l-F{!pv) z(wa@xEB|~|@RWQpw<V%Hw<3Afp`eMICvyF6?ydas<*?|D+YXcB=A2^Wv;Sw75a$xT zZHa-}Qf9Z<-OD#@Ug!5+-}8;<Q-*eh>Vtbaev9Qw?OsyUP?c<M{mskYaiR6I8@~Np ze}wQ^`0D;uJhrFfn@XX>q?vP1Rc-quutWC9M&|8vRWrZz6x7*Bujk>~FL3++_BGq5 z9{rlF#rLt|Q2YfM{l0bLv)<@v-0pb0sLC(S(Pe_HN_6$%9FDz(cJ6k0yHyS;uU~6? ze}>ZfhZC=@4p_8?+y8F;=fZopfBoI&;eMw7$T_2Rp*qr2$^t6l(x;mI{POm1lfei3 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