diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 925f0ee208953cdaf81a265d041bccbc79d9c2b0..24dc5606eca84f06389179d3a7467d1dc1ebc683 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -254,7 +254,7 @@ def udate_down_up(self, context): s = context.scene wm = bpy.context.window_manager props = s.blenderkitUI - if wm['search results'] == None and props.down_up == 'SEARCH': + if wm.get('search results') == None and props.down_up == 'SEARCH': search.search() def switch_search_results(self, context): @@ -574,7 +574,7 @@ class BlenderKitCommonSearchProps(object): def name_update(self, context): ''' checks for name change, because it decides if whole asset has to be re-uploaded. Name is stored in the blend file and that's the reason.''' - utils.name_update() + utils.name_update(self) diff --git a/blenderkit/append_link.py b/blenderkit/append_link.py index 8ec861c93e960a4e98760e8800c0edf13b488fc1..a8b5c7185dbf0a5081ed908206f1d5e7bde79ce3 100644 --- a/blenderkit/append_link.py +++ b/blenderkit/append_link.py @@ -87,6 +87,11 @@ def append_scene(file_name, scenename=None, link=False, fake_user=False): scene.use_fake_user = True # scene has to have a new uuid, so user reports aren't screwed. scene['uuid'] = str(uuid.uuid4()) + + #reset ui_props of the scene to defaults: + ui_props = bpy.context.scene.blenderkitUI + ui_props.down_up = 'SEARCH' + return scene diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py index ab5dd88f55f7240eb8b6e6c98facfd8645da4c9e..699e2c7de7557908cfc23e56eb13f0be37bd851f 100644 --- a/blenderkit/ratings.py +++ b/blenderkit/ratings.py @@ -135,7 +135,7 @@ def update_ratings_work_hours(self, context): bkit_ratings = self url = paths.get_api_url() + f'assets/{self.asset_id}/rating/' - if bkit_ratings.rating_work_hours > 0.05: + 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) @@ -297,7 +297,7 @@ def update_ratings_work_hours_ui_1_5(self, context): class FastRateMenu(Operator): """Fast rating of the assets directly in the asset bar - without need to download assets""" bl_idname = "wm.blenderkit_menu_rating_upload" - bl_label = "Send Rating" + bl_label = "Rate asset" bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} message: StringProperty( @@ -340,6 +340,8 @@ class FastRateMenu(Operator): min=0.0, max=300, update=update_ratings_work_hours ) + 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', ''), @@ -354,11 +356,12 @@ class FastRateMenu(Operator): ('10', '10', ''), ('15', '15', ''), ('20', '20', ''), - ('50', '50', ''), - ('100', '100', ''), - ('150', '150', ''), - ('200', '200', ''), - ('250', '250', ''), + ('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=update_ratings_work_hours_ui ) @@ -391,14 +394,34 @@ class FastRateMenu(Operator): 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() - col.prop(self, 'rating_work_hours') - if utils.profile_is_validator(): + + 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) + + else: + + row = layout.row() - if self.asset_type == 'model': - row.prop(self, 'rating_work_hours_ui', expand=True, icon_only=False, emboss=True) - else: - row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True) + row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True) + def execute(self, context): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences @@ -414,13 +437,13 @@ class FastRateMenu(Operator): if self.rating_quality_ui == '': self.rating_quality = 0 else: - self.rating_quality = int(self.rating_quality_ui) + self.rating_quality = float(self.rating_quality_ui) if self.rating_quality > 0.1: rtgs = (('quality', self.rating_quality),) tasks_queue.add_task((send_rating_to_thread_quality, (url, rtgs, headers)), wait=2.5, only_last=True) - if self.rating_work_hours > 0.1: + if self.rating_work_hours > 0.45: rtgs = (('working_hours', round(self.rating_work_hours, 1)),) tasks_queue.add_task((send_rating_to_thread_work_hours, (url, rtgs, headers)), wait=2.5, only_last=True) return {'FINISHED'} @@ -437,7 +460,7 @@ class FastRateMenu(Operator): self.message = f"Rate asset {self.asset_name}" wm = context.window_manager - if utils.profile_is_validator() and self.asset_type == 'model': + 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/search.py b/blenderkit/search.py index 836c29b0e695d2d560b30a71183bf8a3a53d90ea..86d2f31cb5ad6035acb01c4c3f701be11bf14cb6 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -660,6 +660,7 @@ def generate_tooltip(mdata): 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) @@ -671,22 +672,27 @@ def generate_tooltip(mdata): # t += '\n' rc = mdata.get('ratingsCount') - if utils.profile_is_validator() and rc: - + if rc: + t+='\n' if rc: rcount = min(rc['quality'], rc['workingHours']) else: rcount = 0 - if rcount < 10: - t += f"Please rate this asset, \nit doesn't have enough ratings.\n" - else: - t += f"Quality rating: {int(mdata['ratingsAverage']['quality']) * '*'}\n" - t += f"Hours saved rating: {int(mdata['ratingsAverage']['workingHours'])}\n" + + show_rating_threshold = 5 + + if rcount < show_rating_threshold: + 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']}*/{mdata['ratingsAverage']['workingHours']}wh\n" + f"{(mdata['ratingsAverage']['quality'],1)}*/{(mdata['ratingsAverage']['workingHours'],1)}wh\n" if len(t.split('\n')) < 11: t += '\n' t += get_random_tip(mdata) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index b0027eacbb5c2ac0a21c69610f2197064399f345..6c5ca9d5ef7436a7d8074e6aa2f756fae9e976d1 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -338,7 +338,7 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None): textcol = (textcol[0], textcol[1], textcol[2], 1) textcol_mild = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1) textcol_strong = (textcol[0] * 1.3, textcol[1] * 2.3, textcol[2] * 1.3, 1) - textcol_strong = (0.4, 1, 0.3, 1) + # textcol_strong = (0.4, 1, 0.3, 1) white = (1, 1, 1, .1) # background diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 302e7119d2d8a66791bf09e3952503e2067ab820..5fd9e07eac17235c4db2b5a6e03764267da6d215 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -409,8 +409,8 @@ class VIEW3D_PT_blenderkit_model_properties(Panel): layout.label(text=str(ad['name'])) if o.instance_type == 'COLLECTION' and o.instance_collection is not None: layout.operator('object.blenderkit_bring_to_scene', text='Bring to scene') - layout.label(text='Ratings:') - draw_panel_model_rating(self, context) + # layout.label(text='Ratings:') + # draw_panel_model_rating(self, context) layout.label(text='Asset tools:') draw_asset_context_menu(self.layout, context, ad, from_panel=True) @@ -1068,32 +1068,6 @@ class VIEW3D_PT_blenderkit_unified(Panel): else: layout.label(text='Switch to paint or sculpt mode.') - elif ui_props.down_up == 'RATING': # the poll functions didn't work here, don't know why. - - if ui_props.asset_type == 'MODEL': - # TODO improve poll here to parenting structures - if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.get( - 'asset_data') != None: - ad = bpy.context.active_object.get('asset_data') - layout.label(text=ad['name']) - draw_panel_model_rating(self, context) - if ui_props.asset_type == 'MATERIAL': - if bpy.context.view_layer.objects.active is not None and \ - bpy.context.active_object.active_material is not None and \ - bpy.context.active_object.active_material.blenderkit.asset_base_id != '': - layout.label(text=bpy.context.active_object.active_material.blenderkit.name + ' :') - # noinspection PyCallByClass - draw_panel_material_ratings(self, context) - if ui_props.asset_type == 'BRUSH': - if context.sculpt_object or context.image_paint_object: - props = utils.get_brush_props(context) - if props.asset_base_id != '': - layout.label(text=props.name + ' :') - # noinspection PyCallByClass - draw_panel_brush_ratings(self, context) - if ui_props.asset_type == 'TEXTURE': - layout.label(text='not yet implemented') - class BlenderKitWelcomeOperator(bpy.types.Operator): """Login online on BlenderKit webpage""" diff --git a/blenderkit/upload.py b/blenderkit/upload.py index 1901d92cc952ad5f36303daec6f4b6b556e86b6b..110994e38880fe17b9d05694e3be142442e1ffb4 100644 --- a/blenderkit/upload.py +++ b/blenderkit/upload.py @@ -467,7 +467,7 @@ def get_upload_data(caller=None, context=None, asset_type=None): add_version(upload_data) # caller can be upload operator, but also asset bar called from tooltip generator - if caller and caller.main_file == True: + if caller and caller.properties.main_file == True: upload_data["name"] = props.name upload_data["displayName"] = props.name else: @@ -536,6 +536,32 @@ def patch_individual_metadata(asset_id, metadata_dict, api_key): # op = layout.operator('wm.blenderkit_fast_metadata', text = ch['name']) +def update_free_full(self, context): + if self.asset_type == 'material': + if self.free_full == 'FULL': + self.free_full = 'FREE' + ui_panels.ui_message(title = "All BlenderKit materials are free", + message = "Any material uploaded to BlenderKit is free." \ + " However, it can still earn money for the author," \ + " based on our fair share system. " \ + "Part of subscription is sent to artists based on usage by paying users.") + +def can_edit_asset(active_index = -1, asset_data = None): + if active_index == -1 and not asset_data: + return False + profile = bpy.context.window_manager.get('bkit profile') + if profile is None: + return False + if utils.profile_is_validator(): + return True + if not asset_data: + 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']: + return True + return False + class FastMetadata(bpy.types.Operator): """Fast change of the category of object directly in asset bar.""" bl_idname = "wm.blenderkit_fast_metadata" @@ -594,11 +620,22 @@ class FastMetadata(bpy.types.Operator): default="PUBLIC", ) + free_full:EnumProperty( + name="Free or Full Plan", + items=( + ('FREE', 'Free', "You consent you want to release this asset as free for everyone"), + ('FULL', 'Full', 'Your asset will be in the full plan') + ), + description="Choose whether the asset should be free or in the Full Plan", + default="FULL", + update=update_free_full + ) + @classmethod def poll(cls, context): scene = bpy.context.scene ui_props = scene.blenderkitUI - return ui_props.active_index > -1 + return can_edit_asset(active_index=ui_props.active_index) def draw(self, context): layout = self.layout @@ -617,6 +654,7 @@ class FastMetadata(bpy.types.Operator): layout.prop(self, 'description') layout.prop(self, 'tags') layout.prop(self, 'is_private', expand=True) + layout.prop(self, 'free_full', expand=True) if self.is_private == 'PUBLIC': layout.prop(self, 'license') @@ -639,6 +677,7 @@ class FastMetadata(bpy.types.Operator): 'description': self.description, 'tags': comma2array(self.tags), 'isPrivate': self.is_private == 'PRIVATE', + 'isFree': self.free_full == 'FREE', 'license': self.license, } @@ -661,6 +700,8 @@ class FastMetadata(bpy.types.Operator): if result['id'] == self.asset_id: asset_data = dict(result) + if not can_edit_asset(asset_data=asset_data): + return {'CANCELLED'} self.asset_id = asset_data['id'] self.asset_type = asset_data['assetType'] cat_path = categories.get_category_path(bpy.context.window_manager['bkit_categories'], @@ -680,6 +721,11 @@ class FastMetadata(bpy.types.Operator): self.is_private = 'PRIVATE' else: self.is_private = 'PUBLIC' + + if asset_data['isFree']: + self.free_full = 'FREE' + else: + self.free_full = 'FULL' self.license = asset_data['license'] wm = context.window_manager @@ -940,6 +986,10 @@ class Uploader(threading.Thread): "file_path": fpath }) + if not os.path.exists(fpath): + self.send_message ("File packing failed, please try manual packing first") + return {'CANCELLED'} + self.send_message('Uploading files') uploaded = upload_bg.upload_files(self.upload_data, files) @@ -994,9 +1044,10 @@ def start_upload(self, context, asset_type, reupload, upload_set): '''start upload process, by processing data, then start a thread that cares about the rest of the upload.''' # fix the name first - utils.name_update() - props = utils.get_upload_props() + + utils.name_update(props) + storage_quota_ok = check_storage_quota(props) if not storage_quota_ok: self.report({'ERROR_INVALID_INPUT'}, props.report) @@ -1022,8 +1073,7 @@ def start_upload(self, context, asset_type, reupload, upload_set): props.id = '' export_data, upload_data = get_upload_data(caller=self, context=context, asset_type=asset_type) - # print(export_data) - # print(upload_data) + # check if thumbnail exists, generate for HDR: if 'THUMBNAIL' in upload_set: if asset_type == 'HDR': @@ -1138,6 +1188,11 @@ class UploadOperator(Operator): if self.main_file: upload_set.append('MAINFILE') + #this is accessed later in get_upload_data and needs to be written. + # should pass upload_set all the way to it probably + if 'MAINFILE' in upload_set: + self.main_file = True + result = start_upload(self, context, self.asset_type, self.reupload, upload_set=upload_set, ) return {'FINISHED'} @@ -1215,6 +1270,8 @@ class AssetDebugPrint(Operator): if ad: result = ad.to_dict() if result: + t = bpy.data.texts.new(result['name']) + t.write(json.dumps(result, indent=4, sort_keys=True)) print(json.dumps(result, indent=4, sort_keys=True)) return {'FINISHED'} diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 245d52363152c8402c03375548019411692e654a..26ace999b54541e644a93dff66e4d36cd09f8569 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -43,6 +43,7 @@ def experimental_enabled(): preferences = bpy.context.preferences.addons['blenderkit'].preferences return preferences.experimental_features + def get_process_flags(): flags = BELOW_NORMAL_PRIORITY_CLASS if sys.platform != 'win32': # TODO test this on windows @@ -215,7 +216,7 @@ def get_upload_props(): return s.blenderkit if ui_props.asset_type == 'HDR': - hdr = ui_props.hdr_upload_image#bpy.data.images.get(ui_props.hdr_upload_image) + hdr = ui_props.hdr_upload_image # bpy.data.images.get(ui_props.hdr_upload_image) if not hdr: return None return hdr.blenderkit @@ -254,7 +255,7 @@ def load_prefs(): fpath = paths.BLENDERKIT_SETTINGS_FILENAME if os.path.exists(fpath): try: - with open(fpath, 'r', encoding = 'utf-8') as s: + with open(fpath, 'r', encoding='utf-8') as s: prefs = json.load(s) user_preferences.api_key = prefs.get('API_key', '') user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict()) @@ -286,7 +287,7 @@ def save_prefs(self, context): fpath = paths.BLENDERKIT_SETTINGS_FILENAME if not os.path.exists(paths._presets): os.makedirs(paths._presets) - with open(fpath, 'w', encoding = 'utf-8') as s: + with open(fpath, 'w', encoding='utf-8') as s: json.dump(prefs, s, ensure_ascii=False, indent=4) except Exception as e: print(e) @@ -303,6 +304,7 @@ def uploadable_asset_poll(): return ui_props.hdr_upload_image is not None return True + def get_hidden_texture(img, force_reload=False): # i = get_hidden_image(tpath, bdata_name, force_reload=force_reload) # bdata_name = f".{bdata_name}" @@ -313,7 +315,8 @@ def get_hidden_texture(img, force_reload=False): t.image = img return t -def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace = 'sRGB'): + +def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'): if bdata_name[0] == '.': hidden_name = bdata_name else: @@ -339,13 +342,13 @@ def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace = 'sRGB') img.filepath = tpath img.reload() - image_utils.set_colorspace(img,colorspace) + image_utils.set_colorspace(img, colorspace) elif force_reload: if img.packed_file is not None: img.unpack(method='USE_ORIGINAL') img.reload() - image_utils.set_colorspace(img,colorspace) + image_utils.set_colorspace(img, colorspace) return img @@ -355,7 +358,7 @@ def get_thumbnail(name): img = bpy.data.images.get(name) if img == None: img = bpy.data.images.load(p) - image_utils.set_colorspace(img,'sRGB') + image_utils.set_colorspace(img, 'sRGB') img.name = name img.name = name @@ -378,16 +381,16 @@ def get_brush_props(context): return None -def p(text, text1='', text2='', text3='', text4='', text5='', level = 'DEBUG'): +def p(text, text1='', text2='', text3='', text4='', text5='', level='DEBUG'): '''debug printing depending on blender's debug value''' - if 1:#bpy.app.debug_value != 0: + if 1: # bpy.app.debug_value != 0: # print('-----BKit debug-----\n') # traceback.print_stack() - texts = [text1,text2,text3,text4,text5] + texts = [text1, text2, text3, text4, text5] text = str(text) for t in texts: - if t!= '': + if t != '': text += ' ' + str(t) bk_logger.debug(text) @@ -398,7 +401,7 @@ def copy_asset(fp1, fp2): '''synchronizes the asset between folders, including it's texture subdirectories''' if 1: bk_logger.debug('copy asset') - bk_logger.debug(fp1 +' '+ fp2) + bk_logger.debug(fp1 + ' ' + fp2) if not os.path.exists(fp2): shutil.copyfile(fp1, fp2) bk_logger.debug('copied') @@ -410,7 +413,7 @@ def copy_asset(fp1, fp2): target_subdir = os.path.join(target_dir, subdir.name) if os.path.exists(target_subdir): continue - bk_logger.debug(str(subdir) +' '+ str(target_subdir)) + bk_logger.debug(str(subdir) + ' ' + str(target_subdir)) shutil.copytree(subdir, target_subdir) bk_logger.debug('copied') @@ -638,11 +641,15 @@ def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False bpy.context.view_layer.objects.active = actob -def name_update(): +def name_update(props): + ''' + Update asset name function, gets run also before upload. Makes sure name doesn't change in case of reuploads, + and only displayName gets written to server. + ''' scene = bpy.context.scene ui_props = scene.blenderkitUI - props = get_upload_props() + # props = get_upload_props() if props.name_old != props.name: props.name_changed = True props.name_old = props.name @@ -674,7 +681,6 @@ def get_param(asset_data, parameter_name): return None - def params_to_dict(params): params_dict = {} for p in params: @@ -705,6 +711,7 @@ def dict_to_params(inputs, parameters=None): }) return parameters + def update_tags(self, context): props = self @@ -729,6 +736,7 @@ def update_tags(self, context): if props.tags != ns: props.tags = ns + def user_logged_in(): a = bpy.context.window_manager.get('bkit profile') if a is not None: @@ -826,5 +834,6 @@ def label_multiline(layout, text='', icon='NONE', width=-1): layout.label(text=l, icon=icon) icon = 'NONE' + def trace(): traceback.print_stack() diff --git a/object_collection_manager/__init__.py b/object_collection_manager/__init__.py index cb2c9479f07c660a83c021c3c57d2ccb015b89f1..392c8d73a3c4d78ca60e2abc9d655a408c678fbf 100644 --- a/object_collection_manager/__init__.py +++ b/object_collection_manager/__init__.py @@ -22,7 +22,7 @@ bl_info = { "name": "Collection Manager", "description": "Manage collections and their objects", "author": "Ryan Inch", - "version": (2, 19, 2), + "version": (2, 19, 3), "blender": (2, 80, 0), "location": "View3D - Object Mode (Shortcut - M)", "warning": '', # used for warning icon and text in addons panel diff --git a/object_collection_manager/preferences.py b/object_collection_manager/preferences.py index 601596cd8f2a9896c0f3d16f1127b24a25ef8721..53f4bacc0bde4d88c23de7b4b346a2e46680f6df 100644 --- a/object_collection_manager/preferences.py +++ b/object_collection_manager/preferences.py @@ -40,12 +40,6 @@ def update_qcd_status(self, context): if self.enable_qcd: qcd_init.register_qcd() - if self.enable_qcd_view_hotkeys: - qcd_init.register_qcd_view_hotkeys() - - if self.enable_qcd_view_edit_mode_hotkeys: - qcd_init.register_qcd_view_edit_mode_hotkeys() - else: qcd_init.unregister_qcd()