From a3a1815d36afbccbd45b52c91afc4e543d4154df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Wed, 15 Jul 2020 00:39:45 +0200 Subject: BlenderKit: fix login after token refresh fails. Now offers a popup to login on site, previously only reported about invalid token, which wasn't clear to many users. (cherry picked from commit c52cfd99ff31f7554cc998c69382d1c8dd7ed8ed) --- blenderkit/bkit_oauth.py | 4 ++-- blenderkit/rerequests.py | 5 +++++ blenderkit/search.py | 4 ++-- blenderkit/tasks_queue.py | 13 +++++++++---- blenderkit/ui.py | 34 +++------------------------------- blenderkit/utils.py | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 39 deletions(-) diff --git a/blenderkit/bkit_oauth.py b/blenderkit/bkit_oauth.py index ae90b215..59ed6c8b 100644 --- a/blenderkit/bkit_oauth.py +++ b/blenderkit/bkit_oauth.py @@ -116,7 +116,7 @@ class RegisterLoginOnline(bpy.types.Operator): message: bpy.props.StringProperty( name="Message", description="", - default="You were logged out from BlenderKit. Clicking OK takes you to web login. ") + default="You were logged out from BlenderKit.\n Clicking OK takes you to web login. ") @classmethod def poll(cls, context): @@ -124,7 +124,7 @@ class RegisterLoginOnline(bpy.types.Operator): def draw(self, context): layout = self.layout - utils.label_multiline(layout, text=self.message) + utils.label_multiline(layout, text=self.message, width = 300) def execute(self, context): preferences = bpy.context.preferences.addons['blenderkit'].preferences diff --git a/blenderkit/rerequests.py b/blenderkit/rerequests.py index 3d9a4d75..c655c8c5 100644 --- a/blenderkit/rerequests.py +++ b/blenderkit/rerequests.py @@ -76,6 +76,11 @@ def rerequest(method, url, **kwargs): utils.p('reresult', response.status_code) if response.status_code >= 400: utils.p('reresult', response.text) + else: + tasks_queue.add_task((ui.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) return response diff --git a/blenderkit/search.py b/blenderkit/search.py index 09dfeb65..f6226049 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -72,7 +72,7 @@ def check_errors(rdata): if user_preferences.enable_oauth: bkit_oauth.refresh_token_thread() return False, rdata.get('detail') - return False, 'Missing or wrong api_key in addon preferences' + return False, 'Use login panel to connect your profile.' return True, '' @@ -282,7 +282,7 @@ def timer_update(): search() preferences.first_run = False if preferences.tips_on_start: - ui.get_largest_3dview() + utils.get_largest_3dview() ui.update_ui_size(ui.active_area, ui.active_region) ui.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN) return 3.0 diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py index bbac6d63..a253aa96 100644 --- a/blenderkit/tasks_queue.py +++ b/blenderkit/tasks_queue.py @@ -45,15 +45,16 @@ def get_queue(): return t.task_queue class task_object: - def __init__(self, command = '', arguments = (), wait = 0, only_last = False): + def __init__(self, command = '', arguments = (), wait = 0, only_last = False, fake_context = False): self.command = command self.arguments = arguments self.wait = wait self.only_last = only_last + self.fake_context = fake_context -def add_task(task, wait = 0, only_last = False): +def add_task(task, wait = 0, only_last = False, fake_context = False): q = get_queue() - taskob = task_object(task[0],task[1], wait = wait, only_last = only_last) + taskob = task_object(task[0],task[1], wait = wait, only_last = only_last, fake_context = fake_context) q.put(taskob) @@ -90,7 +91,11 @@ def queue_worker(): utils.p('as a task: ') utils.p(task.command, task.arguments) try: - task.command(*task.arguments) + if task.fake_context: + fc = utils.get_fake_context(bpy.context) + task.command(fc,*task.arguments) + else: + task.command(*task.arguments) except Exception as e: utils.p('task failed:') print(e) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index a1cd66d9..fa26d8a3 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -1181,30 +1181,6 @@ def update_ui_size(area, region): ui.rating_y = ui.bar_y - ui.bar_height -def get_largest_3dview(): - maxsurf = 0 - maxa = None - maxw = None - region = None - for w in bpy.context.window_manager.windows: - screen = w.screen - for a in screen.areas: - if a.type == 'VIEW_3D': - asurf = a.width * a.height - if asurf > maxsurf: - maxa = a - maxw = w - maxsurf = asurf - - for r in a.regions: - if r.type == 'WINDOW': - region = r - global active_area, active_window, active_region - active_window = maxw - active_area = maxa - active_region = region - return maxw, maxa, region - class AssetBarOperator(bpy.types.Operator): '''runs search and displays the asset bar at the same time''' @@ -1808,13 +1784,14 @@ class UndoWithContext(bpy.types.Operator): C_dict = bpy.context.copy() C_dict.update(region='WINDOW') if context.area is None or context.area.type != 'VIEW_3D': - w, a, r = get_largest_3dview() + w, a, r = utils.get_largest_3dview() override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} C_dict.update(override) bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message) return {'FINISHED'} + class RunAssetBarWithContext(bpy.types.Operator): """Regenerate cobweb""" bl_idname = "object.run_assetbar_fix_context" @@ -1826,12 +1803,7 @@ class RunAssetBarWithContext(bpy.types.Operator): # return {'RUNNING_MODAL'} def execute(self, context): - C_dict = bpy.context.copy() - C_dict.update(region='WINDOW') - if context.area is None or context.area.type != 'VIEW_3D': - w, a, r = get_largest_3dview() - override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} - C_dict.update(override) + C_dict = utils.get_fake_context(context) bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False) return {'FINISHED'} diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 2e59887c..78eff216 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -614,6 +614,39 @@ def guard_from_crash(): return True +def get_largest_3dview(): + maxsurf = 0 + maxa = None + maxw = None + region = None + for w in bpy.context.window_manager.windows: + screen = w.screen + for a in screen.areas: + if a.type == 'VIEW_3D': + asurf = a.width * a.height + if asurf > maxsurf: + maxa = a + maxw = w + maxsurf = asurf + + for r in a.regions: + if r.type == 'WINDOW': + region = r + global active_area, active_window, active_region + active_window = maxw + active_area = maxa + active_region = region + return maxw, maxa, region + +def get_fake_context(context): + C_dict = context.copy() + C_dict.update(region='WINDOW') + if context.area is None or context.area.type != 'VIEW_3D': + w, a, r = get_largest_3dview() + override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} + C_dict.update(override) + return C_dict + def label_multiline(layout, text='', icon='NONE', width=-1): ''' draw a ui label, but try to split it in multiple lines.''' if text.strip() == '': -- cgit v1.2.3 From 66bd6dea71862d8f55ede79879117d9e7de882a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Wed, 15 Jul 2020 19:06:53 +0200 Subject: BlenderKit: on-registration popup This popup informs the user that BlenderKit connects to the internet directly after registration, and asks for consent with it and also performs first search. (cherry picked from commit 00fefe2d147288e3a218d640668b54331e82d3e8) --- blenderkit/__init__.py | 5 +++++ blenderkit/search.py | 8 +++---- blenderkit/tasks_queue.py | 9 ++++---- blenderkit/ui.py | 2 +- blenderkit/ui_panels.py | 56 +++++++++++++++++++++++++++++++++++++++++++++-- blenderkit/utils.py | 16 ++++++++------ 6 files changed, 78 insertions(+), 18 deletions(-) diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index a7e148ea..23ee89c5 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -1725,6 +1725,11 @@ def register(): bpy.app.timers.register(check_timers_timer, persistent=True) bpy.app.handlers.load_post.append(scene_load) + # detect if the user just enabled the addon in preferences, thus enable to run + for w in bpy.context.window_manager.windows: + for a in w.screen.areas: + if a.type == 'PREFERENCES': + tasks_queue.add_task((bpy.ops.wm.blenderkit_welcome,( 'INVOKE_DEFAULT',)),fake_context = True, fake_context_area = 'PREFERENCES') def unregister(): diff --git a/blenderkit/search.py b/blenderkit/search.py index f6226049..cee0b14b 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -282,14 +282,14 @@ def timer_update(): search() preferences.first_run = False if preferences.tips_on_start: - utils.get_largest_3dview() + utils.get_largest_area() ui.update_ui_size(ui.active_area, ui.active_region) ui.add_report(text='BlenderKit Tip: ' + random.choice(rtips), timeout=12, color=colors.GREEN) return 3.0 - if preferences.first_run: - search() - preferences.first_run = False + # if preferences.first_run: + # search() + # preferences.first_run = False # check_clipboard() diff --git a/blenderkit/tasks_queue.py b/blenderkit/tasks_queue.py index a253aa96..5a327290 100644 --- a/blenderkit/tasks_queue.py +++ b/blenderkit/tasks_queue.py @@ -45,16 +45,17 @@ def get_queue(): return t.task_queue class task_object: - def __init__(self, command = '', arguments = (), wait = 0, only_last = False, fake_context = False): + def __init__(self, command = '', arguments = (), wait = 0, only_last = False, fake_context = False, fake_context_area = 'VIEW_3D'): self.command = command self.arguments = arguments self.wait = wait self.only_last = only_last self.fake_context = fake_context + self.fake_context_area = fake_context_area -def add_task(task, wait = 0, only_last = False, fake_context = False): +def add_task(task, wait = 0, only_last = False, fake_context = False, fake_context_area = 'VIEW_3D'): q = get_queue() - taskob = task_object(task[0],task[1], wait = wait, only_last = only_last, fake_context = fake_context) + taskob = task_object(task[0],task[1], wait = wait, only_last = only_last, fake_context = fake_context, fake_context_area = fake_context_area) q.put(taskob) @@ -92,7 +93,7 @@ def queue_worker(): utils.p(task.command, task.arguments) try: if task.fake_context: - fc = utils.get_fake_context(bpy.context) + fc = utils.get_fake_context(bpy.context, area_type = task.fake_context_area) task.command(fc,*task.arguments) else: task.command(*task.arguments) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index fa26d8a3..30195168 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -1784,7 +1784,7 @@ class UndoWithContext(bpy.types.Operator): C_dict = bpy.context.copy() C_dict.update(region='WINDOW') if context.area is None or context.area.type != 'VIEW_3D': - w, a, r = utils.get_largest_3dview() + w, a, r = utils.get_largest_area() override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} C_dict.update(override) bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message) diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index b8d8ce73..d067afa0 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -25,12 +25,22 @@ if "bpy" in locals(): download = importlib.reload(download) categories = importlib.reload(categories) icons = importlib.reload(icons) + icons = importlib.reload(search) else: - from blenderkit import paths, ratings, utils, download, categories, icons + from blenderkit import paths, ratings, utils, download, categories, icons, search from bpy.types import ( Panel ) +from bpy.props import ( + IntProperty, + FloatProperty, + FloatVectorProperty, + StringProperty, + EnumProperty, + BoolProperty, + PointerProperty, +) import bpy import os @@ -962,6 +972,47 @@ class VIEW3D_PT_blenderkit_unified(Panel): if ui_props.asset_type == 'TEXTURE': layout.label(text='not yet implemented') +class BlenderKitWelcomeOperator(bpy.types.Operator): + """Login online on BlenderKit webpage""" + + bl_idname = "wm.blenderkit_welcome" + bl_label = "Welcome to BlenderKit!" + bl_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + step: IntProperty( + name="step", + description="Tutorial Step", + default=0, + options={'SKIP_SAVE'} + ) + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + if self.step == 0: + message = "BlenderKit is an addon that connects to the internet to search and upload for models, materials, and brushes. \n\n Let's start by searching for some cool materials?" + else: + message = "This shouldn't be here at all" + utils.label_multiline(layout, text= message, width = 300) + + def execute(self, context): + if self.step == 0: + #move mouse: + #bpy.context.window_manager.windows[0].cursor_warp(1000, 1000) + #show n-key sidebar (spaces[index] has to be found for view3d too: + # bpy.context.window_manager.windows[0].screen.areas[5].spaces[0].show_region_ui = False + print('running search no') + ui_props = bpy.context.scene.blenderkitUI + ui_props.asset_type = 'MATERIAL' + search.search() + return {'FINISHED'} + + def invoke(self, context, event): + wm = bpy.context.window_manager + return wm.invoke_props_dialog(self) def draw_asset_context_menu(self, context, asset_data): layout = self.layout @@ -1298,7 +1349,8 @@ classess = ( VIEW3D_PT_blenderkit_downloads, OBJECT_MT_blenderkit_asset_menu, OBJECT_MT_blenderkit_login_menu, - UrlPopupDialog + UrlPopupDialog, + BlenderKitWelcomeOperator, ) diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 78eff216..effc2627 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -614,15 +614,14 @@ def guard_from_crash(): return True -def get_largest_3dview(): +def get_largest_area( area_type = 'VIEW_3D'): maxsurf = 0 maxa = None maxw = None region = None for w in bpy.context.window_manager.windows: - screen = w.screen - for a in screen.areas: - if a.type == 'VIEW_3D': + for a in w.screen.areas: + if a.type == area_type: asurf = a.width * a.height if asurf > maxsurf: maxa = a @@ -638,15 +637,18 @@ def get_largest_3dview(): active_region = region return maxw, maxa, region -def get_fake_context(context): +def get_fake_context(context, area_type = 'VIEW_3D'): C_dict = context.copy() C_dict.update(region='WINDOW') - if context.area is None or context.area.type != 'VIEW_3D': - w, a, r = get_largest_3dview() + if context.area is None or context.area.type != area_type: + w, a, r = get_largest_area(area_type = area_type) + override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} C_dict.update(override) + # print(w,a,r) return C_dict + def label_multiline(layout, text='', icon='NONE', width=-1): ''' draw a ui label, but try to split it in multiple lines.''' if text.strip() == '': -- cgit v1.2.3 From 06181ee9947d558d5323471c9b5d9792267fdb66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Mon, 27 Jul 2020 12:59:38 +0200 Subject: BlenderKit: Rating refactorings This mainly paves a way for removing the old and clumsy bgl UI and enable faster rating for users. -Rating Ui is now more responsive -it can be dragged over the stars widget. -fast rating operator (f shortcut over assetbar) -wip on new ratings panel(disabled by now) -if author didn't provide his webpage, the link now leads to his profile on the BlenderKit site. -upload was partially broken thanks to a small bug -perpendicular snap - This limits angled snapping in a reasonable way, should help when placing foliage or items on sloped ceilings e.t.c. -removed the first_run property, it's replaced with a poput that informs the user about connecting to the internet. (cherry picked from commit 8f6903bc92531aa8e5d4c64a0a108c2904506a83) --- blenderkit/__init__.py | 64 +++++------- blenderkit/paths.py | 3 + blenderkit/ratings.py | 269 ++++++++++++++++++++++++++++++++++++++---------- blenderkit/search.py | 10 +- blenderkit/ui.py | 28 +++-- blenderkit/ui_panels.py | 95 ++++++++++------- blenderkit/upload.py | 2 +- 7 files changed, 328 insertions(+), 143 deletions(-) diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 23ee89c5..fba80a7e 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -250,6 +250,7 @@ def switch_search_results(self, context): s['search results orig'] = s.get('bkit brush search orig') search.load_previews() + def asset_type_callback(self, context): ''' Returns @@ -650,29 +651,6 @@ class BlenderKitCommonUploadProps(object): ) -def stars_enum_callback(self, context): - 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(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_quality_ui = '0' - self.rating_quality = int(self.rating_quality_ui) - - class BlenderKitRatingProps(PropertyGroup): rating_quality: IntProperty(name="Quality", description="quality of the material", @@ -680,19 +658,20 @@ class BlenderKitRatingProps(PropertyGroup): min=-1, max=10, update=ratings.update_ratings_quality) - #the following enum is only to ease interaction - enums support 'drag over' and enable to draw the stars easily. + # 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, - description='Rating stars 0 - 10', - default=None, - update=update_quality, - ) + items=ratings.stars_enum_callback, + description='Rating stars 0 - 10', + default=None, + update=ratings.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=1000, update=ratings.update_ratings_work_hours ) + # rating_complexity: IntProperty(name="Complexity", # description="Complexity is a number estimating how much work was spent on the asset.aaa", # default=0, min=0, max=10) @@ -1393,6 +1372,17 @@ class BlenderKitModelSearchProps(PropertyGroup, BlenderKitCommonSearchProps): max=180, subtype='ANGLE') + perpendicular_snap: BoolProperty(name='Perpendicular snap', + description="Limit snapping that is close to perpendicular angles to be perpendicular.", + default=True) + + perpendicular_snap_threshold: FloatProperty(name="Threshold", + description="Limit perpendicular snap to be below these values.", + default=.25, + min=0, + max=.5, + ) + class BlenderKitSceneSearchProps(PropertyGroup, BlenderKitCommonSearchProps): search_keywords: StringProperty( @@ -1586,12 +1576,13 @@ class BlenderKitAddonPreferences(AddonPreferences): min=0, max=20000) - first_run: BoolProperty( - name="First run", - description="Detects if addon was already registered/run.", - default=True, - update=utils.save_prefs - ) + # this is now made obsolete by the new popup upon registration -ensures the user knows about the first search. + # first_run: BoolProperty( + # name="First run", + # description="Detects if addon was already registered/run.", + # default=True, + # update=utils.save_prefs + # ) use_timers: BoolProperty( name="Use timers", @@ -1729,7 +1720,8 @@ def register(): for w in bpy.context.window_manager.windows: for a in w.screen.areas: if a.type == 'PREFERENCES': - tasks_queue.add_task((bpy.ops.wm.blenderkit_welcome,( 'INVOKE_DEFAULT',)),fake_context = True, fake_context_area = 'PREFERENCES') + tasks_queue.add_task((bpy.ops.wm.blenderkit_welcome, ('INVOKE_DEFAULT',)), fake_context=True, + fake_context_area='PREFERENCES') def unregister(): diff --git a/blenderkit/paths.py b/blenderkit/paths.py index b4210a85..399e7555 100644 --- a/blenderkit/paths.py +++ b/blenderkit/paths.py @@ -75,6 +75,9 @@ def get_api_url(): def get_oauth_landing_url(): return get_bkit_url() + BLENDERKIT_OAUTH_LANDING_URL +def get_author_gallery_url(author_id): + return f'{get_bkit_url()}/asset-gallery?query=author_id:{author_id}' + def default_global_dict(): from os.path import expanduser diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py index 48c34a61..800749c8 100644 --- a/blenderkit/ratings.py +++ b/blenderkit/ratings.py @@ -94,7 +94,7 @@ def upload_review_thread(url, reviews, headers): def get_rating(asset_id): - #this function isn't used anywhere,should probably get removed. + # this function isn't used anywhere,should probably get removed. user_preferences = bpy.context.preferences.addons['blenderkit'].preferences api_key = user_preferences.api_key headers = utils.get_headers(api_key) @@ -114,8 +114,13 @@ def update_ratings_quality(self, context): headers = utils.get_headers(api_key) asset = self.id_data - bkit_ratings = asset.bkit_ratings - url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/' + if 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)] @@ -127,15 +132,19 @@ def update_ratings_work_hours(self, context): api_key = user_preferences.api_key headers = utils.get_headers(api_key) asset = self.id_data - bkit_ratings = asset.bkit_ratings - url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/' + if 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.05: ratings = [('working_hours', round(bkit_ratings.rating_work_hours, 1))] tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=1, only_last=True) - def upload_rating(asset): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences api_key = user_preferences.api_key @@ -173,6 +182,7 @@ def upload_rating(asset): if bkit_ratings.rating_quality > 0.1 and bkit_ratings.rating_work_hours > 0.1: s['assets rated'][asset['asset_data']['assetBaseId']] = True + def get_assets_for_rating(): ''' gets assets from scene that could/should be rated by the user. @@ -191,26 +201,6 @@ def get_assets_for_rating(): assets.append(b) return assets -# class StarRatingOperator(bpy.types.Operator): -# """Tooltip""" -# bl_idname = "object.blenderkit_rating" -# bl_label = "Rate the Asset Quality" -# bl_options = {'REGISTER', 'INTERNAL'} -# -# property_name: StringProperty( -# name="Rating Property", -# description="Property that is rated", -# default="", -# ) -# -# rating: IntProperty(name="Rating", description="rating value", default=1, min=1, max=10) -# -# def execute(self, context): -# asset = utils.get_active_asset() -# props = asset.bkit_ratings -# props.rating_quality = self.rating -# return {'FINISHED'} - asset_types = ( ('MODEL', 'Model', 'set of objects'), @@ -254,43 +244,212 @@ 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 draw_rating(layout, props, prop_name, name): - # layout.label(name) - - row = layout.row(align=True) - # test method - 10 booleans. - # propsx = bpy.context.active_object.bkit_ratings - # for a in range(0, 10): - # pn = f'rq{str(a+1).zfill(2)}' - # if eval('propsx.' + pn) == False: - # icon = 'SOLO_OFF' - # else: - # icon = 'SOLO_ON' - # row.prop(propsx, pn, icon=icon, icon_only=True) - # print(dir(props)) - # new best method - enum with an items callback. ('animates' the stars as item icons) - row.prop(props, 'rating_quality_ui', expand=True, icon_only=True, emboss = False) - # original (operator) method: - # row = layout.row(align=True) - # for a in range(0, 10): - # if eval('props.' + prop_name) < a + 1: - # icon = 'SOLO_OFF' - # else: - # icon = 'SOLO_ON' - # - # op = row.operator('object.blenderkit_rating', icon=icon, emboss=False, text='') - # op.property_name = prop_name - # op.rating = a + 1 +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.update_ratings_work_hours_ui_1_5 = '0' + self.rating_work_hours = float(self.update_ratings_work_hours_ui_1_5) + + + +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_options = {'REGISTER', 'UNDO', 'INTERNAL'} + + message: StringProperty( + name="message", + description="message", + default="Rating asset") + + asset_id: StringProperty( + name="Asset Base Id", + description="Unique name of the asset (hidden)", + default="") + + asset_type: StringProperty( + name="Asset type", + description="asset type", + default="") + + rating_quality: IntProperty(name="Quality", + description="quality of the material", + default=0, + min=-1, max=10, + update=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=stars_enum_callback, + description='Rating stars 0 - 10', + default=None, + update=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=1000, update=update_ratings_work_hours + ) + + 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', ''), + ('10', '10', ''), + ('15', '15', ''), + ('20', '20', ''), + ('50', '50', ''), + ('100', '100', ''), + ], + default='0', update=update_ratings_work_hours_ui + ) + + 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=update_ratings_work_hours_ui_1_5 + ) + + @classmethod + def poll(cls, context): + scene = bpy.context.scene + ui_props = scene.blenderkitUI + return ui_props.active_index > -1 + + 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) + col.separator() + col.prop(self, 'rating_work_hours') + row = col.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) + + def execute(self, context): + user_preferences = bpy.context.preferences.addons['blenderkit'].preferences + api_key = user_preferences.api_key + headers = utils.get_headers(api_key) + + url = paths.get_api_url() + f'assets/{self.asset_id}/rating/' + + rtgs = [ + + ] + + self.rating_quality = int(self.rating_quality_ui) + + if self.rating_quality > 0.1: + rtgs.append(('quality', self.rating_quality)) + if self.rating_work_hours > 0.1: + rtgs.append(('working_hours', round(self.rating_work_hours, 1))) + + thread = threading.Thread(target=upload_rating_thread, args=(url, rtgs, headers)) + thread.start() + return {'FINISHED'} + + def invoke(self, context, event): + scene = bpy.context.scene + ui_props = scene.blenderkitUI + if ui_props.active_index > -1: + sr = bpy.context.scene['search results'] + asset_data = dict(sr[ui_props.active_index]) + self.asset_id = asset_data['id'] + self.asset_type = asset_data['assetType'] + self.message = f"Rate asset {asset_data['name']}" + wm = context.window_manager + return wm.invoke_props_dialog(self) + + +def rating_menu_draw(self, context): + layout = self.layout + + ui_props = context.scene.blenderkitUI + sr = bpy.context.scene['search results orig'] + + asset_search_index = ui_props.active_index + if asset_search_index > -1: + asset_data = dict(sr['results'][asset_search_index]) + + col = layout.column() + layout.label(text='Admin rating Tools:') + col.operator_context = 'INVOKE_DEFAULT' + + op = col.operator('wm.blenderkit_menu_rating_upload', text='Fast rate') + op.asset_id = asset_data['id'] + op.asset_type = asset_data['assetType'] + def register_ratings(): - pass; - # bpy.utils.register_class(StarRatingOperator) bpy.utils.register_class(UploadRatingOperator) + bpy.utils.register_class(FastRateMenu) + # bpy.types.OBJECT_MT_blenderkit_asset_menu.append(rating_menu_draw) def unregister_ratings(): pass; # bpy.utils.unregister_class(StarRatingOperator) bpy.utils.unregister_class(UploadRatingOperator) + bpy.utils.unregister_class(FastRateMenu) diff --git a/blenderkit/search.py b/blenderkit/search.py index cee0b14b..6c8d16e2 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -280,7 +280,7 @@ def timer_update(): # TODO here it should check if there are some results, and only open assetbar if this is the case, not search. # if bpy.context.scene.get('search results') is None: search() - preferences.first_run = False + # preferences.first_run = False if preferences.tips_on_start: utils.get_largest_area() ui.update_ui_size(ui.active_area, ui.active_region) @@ -1161,14 +1161,6 @@ def search(category='', get_next=False, author_id=''): scene = bpy.context.scene ui_props = scene.blenderkitUI - ### updating of search categories was moved here, due to the reason that BlenderKit created the blenderkit_data - # folder upon registration of BlenderKit, which wasn't a favourite option for some users (devs running tests). - # user_preferences = bpy.context.preferences.addons['blenderkit'].preferences - # if not user_preferences.first_run: - # api_key = user_preferences.api_key - # if bpy.context.window_manager.get('bkit_categories') is None: - # categories.fetch_categories_thread(api_key) - if ui_props.asset_type == 'MODEL': if not hasattr(scene, 'blenderkit'): return; diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 30195168..7935363d 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -876,7 +876,8 @@ def draw_callback_2d_search(self, context): else: iname = utils.previmg_name(ui_props.active_index) img = bpy.data.images.get(iname) - img.colorspace_settings.name = 'sRGB' + if img: + img.colorspace_settings.name = 'sRGB' gimg = None atip = '' @@ -931,9 +932,21 @@ def mouse_raycast(context, mx, my): # rote = mathutils.Euler((0, 0, math.pi)) randoffset = math.pi if has_hit: - snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler() - up = Vector((0, 0, 1)) props = bpy.context.scene.blenderkit_models + up = Vector((0, 0, 1)) + + if props.perpendicular_snap: + if snapped_normal.z > 1 - props.perpendicular_snap_threshold: + snapped_normal = Vector((0, 0, 1)) + elif snapped_normal.z < -1 + props.perpendicular_snap_threshold: + snapped_normal = Vector((0, 0, -1)) + elif abs(snapped_normal.z) < props.perpendicular_snap_threshold: + snapped_normal.z = 0 + snapped_normal.normalize() + + snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler() + + if props.randomize_rotation and snapped_normal.angle(up) < math.radians(10.0): randoffset = props.offset_rotation_amount + math.pi + ( random.random() - 0.5) * props.randomize_rotation_amount @@ -1668,6 +1681,7 @@ class AssetBarOperator(bpy.types.Operator): utils.p('author:', a) search.search(author_id=a) return {'RUNNING_MODAL'} + if event.type == 'X' and ui_props.active_index > -1: # delete downloaded files for this asset sr = bpy.context.scene['search results'] @@ -1845,13 +1859,15 @@ def register_ui(): if not wm.keyconfigs.addon: return km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type='EMPTY') + #asset bar shortcut kmi = km.keymap_items.new(AssetBarOperator.bl_idname, 'SEMI_COLON', 'PRESS', ctrl=False, shift=False) kmi.properties.keep_running = False kmi.properties.do_search = False addon_keymapitems.append(kmi) - # auto open after searching: - kmi = km.keymap_items.new(RunAssetBarWithContext.bl_idname, 'SEMI_COLON', 'PRESS', \ - ctrl=True, shift=True, alt=True) + #fast rating shortcut + wm = bpy.context.window_manager + km = wm.keyconfigs.addon.keymaps['Window'] + kmi = km.keymap_items.new(ratings.FastRateMenu.bl_idname, 'F', 'PRESS', ctrl=False, shift=False) addon_keymapitems.append(kmi) diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index d067afa0..89646862 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -47,7 +47,6 @@ import os import random - # this was moved to separate interface: def draw_ratings(layout, context, asset): @@ -63,9 +62,8 @@ def draw_ratings(layout, context, asset): # layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0) row = col.row() - row.prop(bkit_ratings , 'rating_quality_ui', expand=True, icon_only=True, emboss=False) - #ratings.draw_rating(col, bkit_ratings, 'rating_quality', 'Quality') - if bkit_ratings.rating_quality>0: + row.prop(bkit_ratings, 'rating_quality_ui', expand=True, icon_only=True, emboss=False) + if bkit_ratings.rating_quality > 0: col.separator() col.prop(bkit_ratings, 'rating_work_hours') # w = context.region.width @@ -81,7 +79,7 @@ def draw_ratings(layout, context, asset): # re-enable layout if included in longer panel -def draw_not_logged_in(source, message = 'Please Login/Signup to use this feature' ): +def draw_not_logged_in(source, message='Please Login/Signup to use this feature'): title = "You aren't logged in" def draw_message(source, context): @@ -104,7 +102,7 @@ def draw_upload_common(layout, props, asset_type, context): row = layout.row(align=True) if props.upload_state != '': - utils.label_multiline(layout, text=props.upload_state, width=context.region.width) + utils.label_multiline(layout, text=props.upload_state, width=context.region.width) if props.uploading: op = layout.operator('object.kill_bg_process', text="", icon='CANCEL') op.process_source = asset_type @@ -193,7 +191,7 @@ def draw_panel_model_upload(self, context): op.process_source = 'MODEL' op.process_type = 'THUMBNAILER' elif props.thumbnail_generating_state != '': - utils.label_multiline(layout, text=props.thumbnail_generating_state) + utils.label_multiline(layout, text=props.thumbnail_generating_state) layout.prop(props, 'description') layout.prop(props, 'tags') @@ -359,7 +357,8 @@ class VIEW3D_PT_blenderkit_model_properties(Panel): o = utils.get_active_model() # o = bpy.context.active_object if o.get('asset_data') is None: - utils.label_multiline(layout, text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.') + utils.label_multiline(layout, + text='To upload this asset to BlenderKit, go to the Find and Upload Assets panel.') layout.prop(o, 'name') if o.get('asset_data') is not None: @@ -383,12 +382,14 @@ class VIEW3D_PT_blenderkit_model_properties(Panel): # fun override project, not finished # layout.operator('object.blenderkit_color_corrector') -def draw_rating_asset(self,context,asset): + +def draw_rating_asset(self, context, asset): layout = self.layout col = layout.box() # split = layout.split(factor=0.5) # col1 = split.column() # col2 = split.column() + # print('%s_search' % asset['asset_data']['assetType']) directory = paths.get_temp_dir('%s_search' % asset['asset_data']['assetType']) tpath = os.path.join(directory, asset['asset_data']['thumbnail_small']) for image in bpy.data.images: @@ -401,8 +402,6 @@ def draw_rating_asset(self,context,asset): draw_ratings(col, context, asset=asset) - - class VIEW3D_PT_blenderkit_ratings(Panel): bl_category = "BlenderKit" bl_idname = "VIEW3D_PT_blenderkit_ratings" @@ -418,16 +417,17 @@ class VIEW3D_PT_blenderkit_ratings(Panel): return p def draw(self, context): - #TODO make a list of assets inside asset appending code, to happen only when assets are added to the scene. + # TODO make a list of assets inside asset appending code, to happen only when assets are added to the scene. # draw asset properties here layout = self.layout assets = ratings.get_assets_for_rating() - if len(assets)>0: - layout.label(text = 'Help BlenderKit community') - layout.label(text = 'by rating these assets:') + if len(assets) > 0: + layout.label(text='Help BlenderKit community') + layout.label(text='by rating these assets:') for a in assets: - draw_rating_asset(self, context, asset = a) + draw_rating_asset(self, context, asset=a) + def draw_login_progress(layout): layout.label(text='Login through browser') @@ -520,7 +520,7 @@ def draw_panel_model_rating(self, context): # o = bpy.context.active_object o = utils.get_active_model() # print('ratings active',o) - draw_ratings(self.layout, context, asset = o) # , props) + draw_ratings(self.layout, context, asset=o) # , props) # op.asset_type = 'MODEL' @@ -564,7 +564,7 @@ def draw_panel_material_upload(self, context): op.process_source = 'MATERIAL' op.process_type = 'THUMBNAILER' elif props.thumbnail_generating_state != '': - utils.label_multiline(layout, text=props.thumbnail_generating_state) + utils.label_multiline(layout, text=props.thumbnail_generating_state) if bpy.context.scene.render.engine in ('CYCLES', 'BLENDER_EEVEE'): layout.operator("object.blenderkit_material_thumbnail", text='Render thumbnail with Cycles', icon='EXPORT') @@ -634,12 +634,12 @@ def draw_panel_brush_search(self, context): def draw_panel_brush_ratings(self, context): # props = utils.get_brush_props(context) brush = utils.get_active_brush() - draw_ratings(self.layout, context, asset = brush) # , props) + draw_ratings(self.layout, context, asset=brush) # , props) # # op.asset_type = 'BRUSH' -def draw_login_buttons(layout, invoke = False): +def draw_login_buttons(layout, invoke=False): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.login_attempt: @@ -782,7 +782,7 @@ class VIEW3D_PT_blenderkit_categories(Panel): s = context.scene ui_props = s.blenderkitUI mode = True - if ui_props.asset_type == 'BRUSH' and not (context.sculpt_object or context.image_paint_object): + if ui_props.asset_type == 'BRUSH' and not (context.sculpt_object or context.image_paint_object): mode = False return ui_props.down_up == 'SEARCH' and mode @@ -820,6 +820,10 @@ class VIEW3D_PT_blenderkit_import_settings(Panel): layout.prop(props, 'randomize_rotation') if props.randomize_rotation: layout.prop(props, 'randomize_rotation_amount') + layout.prop(props, 'perpendicular_snap') + if props.perpendicular_snap: + layout.prop(props,'perpendicular_snap_threshold') + if ui_props.asset_type == 'MATERIAL': props = s.blenderkit_mat layout.prop(props, 'automap') @@ -924,7 +928,7 @@ class VIEW3D_PT_blenderkit_unified(Panel): return; if ui_props.asset_type == 'MODEL': - #utils.label_multiline(layout, "Uploaded models won't be available in b2.79", icon='ERROR') + # utils.label_multiline(layout, "Uploaded models won't be available in b2.79", icon='ERROR') if bpy.context.view_layer.objects.active is not None: draw_panel_model_upload(self, context) else: @@ -933,12 +937,12 @@ class VIEW3D_PT_blenderkit_unified(Panel): draw_panel_scene_upload(self, context) elif ui_props.asset_type == 'MATERIAL': - #utils.label_multiline(layout, "Uploaded materials won't be available in b2.79", icon='ERROR') + # utils.label_multiline(layout, "Uploaded materials won't be available in b2.79", icon='ERROR') if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None: draw_panel_material_upload(self, context) else: - utils.label_multiline(layout, text='select object with material to upload materials', width=w) + utils.label_multiline(layout, text='select object with material to upload materials', width=w) elif ui_props.asset_type == 'BRUSH': if context.sculpt_object or context.image_paint_object: @@ -972,6 +976,7 @@ class VIEW3D_PT_blenderkit_unified(Panel): if ui_props.asset_type == 'TEXTURE': layout.label(text='not yet implemented') + class BlenderKitWelcomeOperator(bpy.types.Operator): """Login online on BlenderKit webpage""" @@ -993,27 +998,36 @@ class BlenderKitWelcomeOperator(bpy.types.Operator): def draw(self, context): layout = self.layout if self.step == 0: - message = "BlenderKit is an addon that connects to the internet to search and upload for models, materials, and brushes. \n\n Let's start by searching for some cool materials?" + user_preferences = bpy.context.preferences.addons['blenderkit'].preferences + + message = "BlenderKit connects from Blender to an online, " \ + "community built shared library of models, " \ + "materials, and brushes. " \ + "Use addon preferences to set up where files will be saved in the Global directory setting." + + utils.label_multiline(layout, text=message, width=300) + utils.label_multiline(layout, text="\n Let's start by searching for some cool materials?", width=300) else: - message = "This shouldn't be here at all" - utils.label_multiline(layout, text= message, width = 300) + message = "Operator Tutorial called with invalid step" def execute(self, context): if self.step == 0: - #move mouse: - #bpy.context.window_manager.windows[0].cursor_warp(1000, 1000) - #show n-key sidebar (spaces[index] has to be found for view3d too: + # move mouse: + # bpy.context.window_manager.windows[0].cursor_warp(1000, 1000) + # show n-key sidebar (spaces[index] has to be found for view3d too: # bpy.context.window_manager.windows[0].screen.areas[5].spaces[0].show_region_ui = False print('running search no') ui_props = bpy.context.scene.blenderkitUI ui_props.asset_type = 'MATERIAL' - search.search() + bpy.context.scene.blenderkit_mat.search_keywords = 'ice' + # search.search() return {'FINISHED'} def invoke(self, context, event): wm = bpy.context.window_manager return wm.invoke_props_dialog(self) + def draw_asset_context_menu(self, context, asset_data): layout = self.layout ui_props = context.scene.blenderkitUI @@ -1024,10 +1038,11 @@ def draw_asset_context_menu(self, context, asset_data): a = bpy.context.window_manager['bkit authors'].get(author_id) if a is not None: # utils.p('author:', a) + op = layout.operator('wm.url_open', text="Open Author's Website") if a.get('aboutMeUrl') is not None: - op = layout.operator('wm.url_open', text="Open Author's Website") op.url = a['aboutMeUrl'] - + else: + op.url = paths.get_author_gallery_url(a['id']) op = layout.operator('view3d.blenderkit_search', text="Show Assets By Author") op.keywords = '' op.author_id = author_id @@ -1080,6 +1095,8 @@ def draw_asset_context_menu(self, context, asset_data): op.asset_id = asset_data['id'] op.state = 'rejected' + + if author_id == str(profile['user']['id']): layout.label(text='Management tools:') row = layout.row() @@ -1087,9 +1104,13 @@ def draw_asset_context_menu(self, context, asset_data): op = row.operator('object.blenderkit_change_status', text='Delete') op.asset_id = asset_data['id'] op.state = 'deleted' - # else: - # #not an author - can rate - # draw_ratings(layout, context) + + if utils.profile_is_validator(): + layout.label(text='Admin rating Tools:') + + op = layout.operator('wm.blenderkit_menu_rating_upload', text='Fast rate') + op.asset_id = asset_data['id'] + op.asset_type = asset_data['assetType'] class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu): @@ -1104,6 +1125,7 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu): asset_data = sr[ui_props.active_index] draw_asset_context_menu(self, context, asset_data) + class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu): bl_label = "BlenderKit login/signup:" bl_idname = "OBJECT_MT_blenderkit_login_menu" @@ -1188,6 +1210,7 @@ class UrlPopupDialog(bpy.types.Operator): return wm.invoke_props_dialog(self) + class LoginPopupDialog(bpy.types.Operator): """Generate Cycles thumbnail for model assets""" bl_idname = "wm.blenderkit_url_dialog" diff --git a/blenderkit/upload.py b/blenderkit/upload.py index 18e43b8a..14fbe6db 100644 --- a/blenderkit/upload.py +++ b/blenderkit/upload.py @@ -767,7 +767,7 @@ class UploadOperator(Operator): layout.label(text="For updates of thumbnail or model use reupload.") if props.is_private == 'PUBLIC': - ui_panels.label_multiline(layout, text='public assets are validated several hours' + utils.label_multiline(layout, text='public assets are validated several hours' ' or days after upload. Remember always to ' 'test download your asset to a clean file' ' to see if it uploaded correctly.' -- cgit v1.2.3 From fb3d2715ce6f3474fc862fbf5454054730bcb516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Tue, 28 Jul 2020 15:31:28 +0200 Subject: BlenderKit: fix data update -older fines could act as broken (cherry picked from commit e1dae55cca702ef4a140a455d88099d666230c8c) --- blenderkit/append_link.py | 8 ++++++++ blenderkit/download.py | 20 +++++++++++++++++--- blenderkit/search.py | 40 +++++++++++++++++++++++++++++----------- blenderkit/ui.py | 5 +++-- blenderkit/ui_panels.py | 7 ++++++- 5 files changed, 63 insertions(+), 17 deletions(-) diff --git a/blenderkit/append_link.py b/blenderkit/append_link.py index b6bfb791..2301daac 100644 --- a/blenderkit/append_link.py +++ b/blenderkit/append_link.py @@ -88,6 +88,8 @@ def append_scene(file_name, scenename=None, link=False, fake_user=False): def link_collection(file_name, obnames=[], location=(0, 0, 0), link=False, parent = None, **kwargs): '''link an instanced group - model type asset''' sel = utils.selection_get() + print('link collection') + print(kwargs) with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to): scols = [] @@ -115,6 +117,12 @@ def link_collection(file_name, obnames=[], location=(0, 0, 0), link=False, paren main_object.instance_collection = col break; + #sometimes, the lib might already be without the actual link. + if not main_object.instance_collection and kwargs['name']: + col = bpy.data.collections.get(kwargs['name']) + if col: + main_object.instance_collection = col + main_object.name = main_object.instance_collection.name # bpy.ops.wm.link(directory=file_name + "/Collection/", filename=kwargs['name'], link=link, instance_collections=True, diff --git a/blenderkit/download.py b/blenderkit/download.py index 7ec425ce..c14fc84d 100644 --- a/blenderkit/download.py +++ b/blenderkit/download.py @@ -343,6 +343,13 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, parent=kwargs.get('parent')) else: + # parent, newobs = append_link.link_collection(file_names[-1], + # location=downloader['location'], + # rotation=downloader['rotation'], + # link=False, + # name=asset_data['name'], + # parent=kwargs.get('parent')) + parent, newobs = append_link.append_objects(file_names[-1], location=downloader['location'], rotation=downloader['rotation'], @@ -364,10 +371,17 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, name=asset_data['name'], parent=kwargs.get('parent')) else: + # parent, newobs = append_link.link_collection(file_names[-1], + # location=kwargs['model_location'], + # rotation=kwargs['model_rotation'], + # link=False, + # name=asset_data['name'], + # parent=kwargs.get('parent')) parent, newobs = append_link.append_objects(file_names[-1], location=kwargs['model_location'], rotation=kwargs['model_rotation'], link=link, + name=asset_data['name'], parent=kwargs.get('parent')) if parent.type == 'EMPTY' and link: bmin = asset_data['bbox_min'] @@ -723,8 +737,8 @@ def try_finished_append(asset_data, **kwargs): # location=None, material_target for f in file_names: try: os.remove(f) - except: - e = sys.exc_info()[0] + except Exception as e: + # e = sys.exc_info()[0] print(e) pass; done = False @@ -934,7 +948,7 @@ class BlenderkitDownloadOperator(bpy.types.Operator): if self.replace: # cleanup first, assign later. obs = utils.get_selected_replace_adepts() - print(obs) + # print(obs) for ob in obs: print('replace attempt ', ob.name) if self.asset_base_id != '': diff --git a/blenderkit/search.py b/blenderkit/search.py index 6c8d16e2..82bb8f58 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -101,22 +101,40 @@ def refresh_token_timer(): return max(3600, user_preferences.api_key_life - 3600) +def update_ad(ad): + if not ad.get('assetBaseId'): + ad['assetBaseId'] = ad['asset_base_id'] # this should stay ONLY for compatibility with older scenes + ad['assetType'] = ad['asset_type'] # this should stay ONLY for compatibility with older scenes + ad['canDownload'] = ad['can_download'] # this should stay ONLY for compatibility with older scenes + ad['verificationStatus'] = ad['verification_status'] # this should stay ONLY for compatibility with older scenes + ad['author'] = {} + ad['author']['id'] = ad['author_id'] # this should stay ONLY for compatibility with older scenes + return ad def update_assets_data(): # updates assets data on scene load. '''updates some properties that were changed on scenes with older assets. The properties were mainly changed from snake_case to CamelCase to fit the data that is coming from the server. ''' - for ob in bpy.context.scene.objects: - if ob.get('asset_data') != None: - ad = ob['asset_data'] - if not ad.get('assetBaseId'): - ad['assetBaseId'] = ad['asset_base_id'], # this should stay ONLY for compatibility with older scenes - ad['assetType'] = ad['asset_type'], # this should stay ONLY for compatibility with older scenes - ad['canDownload'] = ad['can_download'], # this should stay ONLY for compatibility with older scenes - ad['verificationStatus'] = ad[ - 'verification_status'], # this should stay ONLY for compatibility with older scenes - ad['author'] = {} - ad['author']['id'] = ad['author_id'], # this should stay ONLY for compatibility with older scenes + data = bpy.data + + datablocks = [ + bpy.data.objects, + bpy.data.materials, + bpy.data.brushes, + ] + for dtype in datablocks: + for block in dtype: + if block.get('asset_data') != None: + update_ad(block['asset_data']) + + dicts = [ + 'assets used', + 'assets rated', + ] + for d in dicts: + for k in d.keys(): + update_ad(d[k]) + # bpy.context.scene['assets used'][ad] = ad @persistent diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 7935363d..80b7ef2c 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -1033,9 +1033,10 @@ def is_rating_possible(): m = ao.active_material if m is not None: ad = m.get('asset_data') - if ad is not None: + if ad is not None and ad.get('assetBaseId'): rated = bpy.context.scene['assets rated'].get(ad['assetBaseId']) - return True, rated, m, ad + if rated: + return True, rated, m, ad # if t>2 and t<2.5: # ui_props.rating_on = False diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 89646862..a591e76e 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -1048,7 +1048,12 @@ def draw_asset_context_menu(self, context, asset_data): op.author_id = author_id op = layout.operator('view3d.blenderkit_search', text='Search Similar') - op.keywords = asset_data['name'] + ' ' + asset_data['description'] + ' ' + ' '.join(asset_data['tags']) + #build search string from description and tags: + op.keywords = asset_data['name'] + if asset_data.get('description'): + op.keywords += ' ' + asset_data.get('description') + op.keywords += ' '.join(asset_data.get('tags')) + if asset_data.get('canDownload') != 0: if len(bpy.context.selected_objects) > 0 and ui_props.asset_type == 'MODEL': aob = bpy.context.active_object -- cgit v1.2.3 From 5cad98dce57ca6d9136d2cdfc8599250ad2cb4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Wed, 29 Jul 2020 20:11:17 +0200 Subject: BlenderKit: fix appending -this was broken due to API changes. Also no need for so much magic now since the default append just works well. -fix a bug in previous commit (asset update) (cherry picked from commit 3bdb5f41aea4897762edd09a71b0bfa8b0b7bc10) --- blenderkit/append_link.py | 39 ++++++++++++++++++++++++++++++++++++--- blenderkit/download.py | 32 +++++++++++--------------------- blenderkit/search.py | 13 +++++++++---- blenderkit/ui.py | 2 +- 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/blenderkit/append_link.py b/blenderkit/append_link.py index 2301daac..5af63fe1 100644 --- a/blenderkit/append_link.py +++ b/blenderkit/append_link.py @@ -189,12 +189,44 @@ def append_particle_system(file_name, obnames=[], location=(0, 0, 0), link=False def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs): '''append objects into scene individually''' + #simplified version of append + scene = bpy.context.scene + sel = utils.selection_get() + bpy.ops.object.select_all(action='DESELECT') + + path = file_name + "\\Collection\\" + object_name = kwargs.get('name') + fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D') + bpy.ops.wm.append(fc,filename=object_name, directory=path) + + + return_obs = [] + for ob in bpy.context.scene.objects: + if ob.select_get(): + return_obs.append(ob) + if not ob.parent: + main_object = ob + ob.location = location + + if kwargs.get('rotation'): + main_object.rotation_euler = kwargs['rotation'] + + if kwargs.get('parent') is not None: + main_object.parent = bpy.data.objects[kwargs['parent']] + main_object.matrix_world.translation = location + + bpy.ops.object.select_all(action='DESELECT') + utils.selection_set(sel) + + return main_object, return_obs with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to): sobs = [] - for ob in data_from.objects: - if ob in obnames or obnames == []: - sobs.append(ob) + for col in data_from.collections: + if col == kwargs.get('name'): + for ob in col.objects: + if ob in obnames or obnames == []: + sobs.append(ob) data_to.objects = sobs # data_to.objects = data_from.objects#[name for name in data_from.objects if name.startswith("house")] @@ -221,6 +253,7 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar hidden_objects.append(obj) obj.hide_viewport = False return_obs.append(obj) + # Only after all objects are in scene! Otherwise gets broken relationships if link == True: bpy.ops.object.make_local(type='SELECT_OBJECT') diff --git a/blenderkit/download.py b/blenderkit/download.py index c14fc84d..a93611c0 100644 --- a/blenderkit/download.py +++ b/blenderkit/download.py @@ -307,17 +307,15 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, # copy for override al = sprops.append_link - import_as = sprops.import_as # set consistency for objects already in scene, otherwise this literally breaks blender :) ain = asset_in_scene(asset_data) + # override based on history if ain is not False: if ain == 'LINKED': al = 'LINK' - import_as = 'GROUP' else: al = 'APPEND' - import_as = 'INDIVIDUAL' # first get conditions for append link link = al == 'LINK' @@ -334,7 +332,7 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, name=asset_data['name']) return - if sprops.import_as == 'GROUP': + if link: parent, newobs = append_link.link_collection(file_names[-1], location=downloader['location'], rotation=downloader['rotation'], @@ -343,12 +341,6 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, parent=kwargs.get('parent')) else: - # parent, newobs = append_link.link_collection(file_names[-1], - # location=downloader['location'], - # rotation=downloader['rotation'], - # link=False, - # name=asset_data['name'], - # parent=kwargs.get('parent')) parent, newobs = append_link.append_objects(file_names[-1], location=downloader['location'], @@ -363,7 +355,7 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, parent.empty_display_size = size_min elif kwargs.get('model_location') is not None: - if sprops.import_as == 'GROUP': + if link: parent, newobs = append_link.link_collection(file_names[-1], location=kwargs['model_location'], rotation=kwargs['model_rotation'], @@ -371,18 +363,14 @@ def append_asset(asset_data, **kwargs): # downloaders=[], location=None, name=asset_data['name'], parent=kwargs.get('parent')) else: - # parent, newobs = append_link.link_collection(file_names[-1], - # location=kwargs['model_location'], - # rotation=kwargs['model_rotation'], - # link=False, - # name=asset_data['name'], - # parent=kwargs.get('parent')) parent, newobs = append_link.append_objects(file_names[-1], location=kwargs['model_location'], rotation=kwargs['model_rotation'], link=link, name=asset_data['name'], parent=kwargs.get('parent')) + + #scale Empty for assets, so they don't clutter the scene. if parent.type == 'EMPTY' and link: bmin = asset_data['bbox_min'] bmax = asset_data['bbox_max'] @@ -758,10 +746,12 @@ def asset_in_scene(asset_data): asset_data['file_name'] = ad['file_name'] asset_data['url'] = ad['url'] - c = bpy.data.collections.get(ad['name']) - if c is not None: - if c.users > 0: - return 'LINKED' + #browse all collections since linked collections can have same name. + for c in bpy.data.collections: + if c.name == ad['name']: + #there can also be more linked collections with same name, we need to check id. + if c.library and c.library.get('asset_data') and c.library['asset_data']['assetBaseId'] == id: + return 'LINKED' return 'APPENDED' return False diff --git a/blenderkit/search.py b/blenderkit/search.py index 82bb8f58..25d85d88 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -131,10 +131,15 @@ def update_assets_data(): # updates assets data on scene load. 'assets used', 'assets rated', ] - for d in dicts: - for k in d.keys(): - update_ad(d[k]) - # bpy.context.scene['assets used'][ad] = ad + for s in bpy.data.scenes: + for k in dicts: + d = s.get(k) + if not d: + continue; + + for k in d.keys(): + update_ad(d[k]) + # bpy.context.scene['assets used'][ad] = ad @persistent diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 80b7ef2c..7ae6988a 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -738,7 +738,7 @@ def draw_callback_2d_search(self, context): highlight = (1, 1, 1, .2) # highlight = (1, 1, 1, 0.8) # background of asset bar - if not ui_props.dragging: + if not ui_props.dragging and ui_props.hcount>0: search_results = s.get('search results') search_results_orig = s.get('search results orig') if search_results == None: -- cgit v1.2.3 From 2f73d67f25ccc538717b10d6bfbcafbc3591c372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Thu, 30 Jul 2020 11:27:57 +0200 Subject: BlenderKit: fix several issues caused by context.copy() now creating simple context everywhere instead of the context.copy() which actually: - could not work if other addons were creating any custom subclasses on context - managed to crash blender in my tests. --- blenderkit/ui.py | 7 +------ blenderkit/utils.py | 51 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 7ae6988a..2822b826 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -1796,12 +1796,7 @@ class UndoWithContext(bpy.types.Operator): message: StringProperty('Undo Message', default='BlenderKit operation') def execute(self, context): - C_dict = bpy.context.copy() - C_dict.update(region='WINDOW') - if context.area is None or context.area.type != 'VIEW_3D': - w, a, r = utils.get_largest_area() - override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} - C_dict.update(override) + C_dict = utils.get_fake_context(context) bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message) return {'FINISHED'} diff --git a/blenderkit/utils.py b/blenderkit/utils.py index effc2627..5236aabb 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -32,9 +32,6 @@ import json import os import sys - - - ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000 HIGH_PRIORITY_CLASS = 0x00000080 @@ -42,12 +39,14 @@ IDLE_PRIORITY_CLASS = 0x00000040 NORMAL_PRIORITY_CLASS = 0x00000020 REALTIME_PRIORITY_CLASS = 0x00000100 + def get_process_flags(): flags = BELOW_NORMAL_PRIORITY_CLASS if sys.platform != 'win32': # TODO test this on windows flags = 0 return flags + def activate(ob): bpy.ops.object.select_all(action='DESELECT') ob.select_set(True) @@ -97,11 +96,12 @@ def get_selected_models(): parents.append(ob) done[ob] = True - #if no blenderkit - like objects were found, use the original selection. + # if no blenderkit - like objects were found, use the original selection. if len(parents) == 0: parents = obs return parents + def get_selected_replace_adepts(): ''' Detect all hierarchies that contain either asset data from selection, or selected objects themselves. @@ -127,11 +127,12 @@ def get_selected_replace_adepts(): done[ob] = True # print(parents) - #if no blenderkit - like objects were found, use the original selection. + # if no blenderkit - like objects were found, use the original selection. if len(parents) == 0: parents = obs return parents + def get_search_props(): scene = bpy.context.scene if scene is None: @@ -238,7 +239,7 @@ def load_prefs(): def save_prefs(self, context): # first check context, so we don't do this on registration or blender startup - if not bpy.app.background: #(hasattr kills blender) + if not bpy.app.background: # (hasattr kills blender) user_preferences = bpy.context.preferences.addons['blenderkit'].preferences # we test the api key for length, so not a random accidentally typed sequence gets saved. lk = len(user_preferences.api_key) @@ -263,16 +264,18 @@ def save_prefs(self, context): except Exception as e: print(e) -def get_hidden_texture(tpath, bdata_name, force_reload = False): - i = get_hidden_image(tpath, bdata_name, force_reload = force_reload) + +def get_hidden_texture(tpath, bdata_name, force_reload=False): + i = get_hidden_image(tpath, bdata_name, force_reload=force_reload) bdata_name = f".{bdata_name}" t = bpy.data.textures.get(bdata_name) if t is None: t = bpy.data.textures.new('.test', 'IMAGE') - if t.image!= i: + if t.image != i: t.image = i return t + def get_hidden_image(tpath, bdata_name, force_reload=False): hidden_name = '.%s' % bdata_name img = bpy.data.images.get(hidden_name) @@ -348,16 +351,19 @@ def get_hierarchy(ob): obs.append(o) return obs -def select_hierarchy(ob, state = True): + +def select_hierarchy(ob, state=True): obs = get_hierarchy(ob) for ob in obs: ob.select_set(state) return obs + def delete_hierarchy(ob): obs = get_hierarchy(ob) bpy.ops.object.delete({"selected_objects": obs}) + def get_bounds_snappable(obs, use_modifiers=False): # progress('getting bounds of object(s)') parent = obs[0] @@ -473,13 +479,15 @@ def get_headers(api_key): headers["Authorization"] = "Bearer %s" % api_key return headers + def scale_2d(v, s, p): '''scale a 2d vector with a pivot''' return (p[0] + s[0] * (v[0] - p[0]), p[1] + s[1] * (v[1] - p[1])) -def scale_uvs(ob, scale = 1.0, pivot = Vector((.5,.5))): + +def scale_uvs(ob, scale=1.0, pivot=Vector((.5, .5))): mesh = ob.data - if len(mesh.uv_layers)>0: + if len(mesh.uv_layers) > 0: uv = mesh.uv_layers[mesh.uv_layers.active_index] # Scale a UV map iterating over its coordinates to a given scale and with a pivot point @@ -488,7 +496,7 @@ def scale_uvs(ob, scale = 1.0, pivot = Vector((.5,.5))): # map uv cubic and switch of auto tex space and set it to 1,1,1 -def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False, just_scale = False): +def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False, just_scale=False): from blenderkit import bg_blender as bg s = bpy.context.scene mat_props = s.blenderkit_mat @@ -540,9 +548,10 @@ def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False # by now, it takes the basic uv map = 1 meter. also, it now doeasn't respect more materials on one object, # it just scales whole UV. if just_scale: - scale_uvs(tob, scale=Vector((1/tex_size, 1/tex_size))) + scale_uvs(tob, scale=Vector((1 / tex_size, 1 / tex_size))) bpy.context.view_layer.objects.active = actob + def name_update(): props = get_upload_props() if props.name_old != props.name: @@ -562,12 +571,14 @@ def name_update(): asset = get_active_asset() asset.name = fname + def params_to_dict(params): params_dict = {} for p in params: params_dict[p['parameterType']] = p['value'] return params_dict + def dict_to_params(inputs, parameters=None): if parameters == None: parameters = [] @@ -605,6 +616,7 @@ def profile_is_validator(): return True return False + def guard_from_crash(): '''Blender tends to crash when trying to run some functions with the addon going through unregistration process.''' if bpy.context.preferences.addons.get('blenderkit') is None: @@ -614,7 +626,7 @@ def guard_from_crash(): return True -def get_largest_area( area_type = 'VIEW_3D'): +def get_largest_area(area_type='VIEW_3D'): maxsurf = 0 maxa = None maxw = None @@ -637,11 +649,12 @@ def get_largest_area( area_type = 'VIEW_3D'): active_region = region return maxw, maxa, region -def get_fake_context(context, area_type = 'VIEW_3D'): - C_dict = context.copy() + +def get_fake_context(context, area_type='VIEW_3D'): + C_dict = {} # context.copy() #context.copy was a source of problems - incompatibility with addons that also define context C_dict.update(region='WINDOW') if context.area is None or context.area.type != area_type: - w, a, r = get_largest_area(area_type = area_type) + w, a, r = get_largest_area(area_type=area_type) override = {'window': w, 'screen': w.screen, 'area': a, 'region': r} C_dict.update(override) @@ -675,4 +688,4 @@ def label_multiline(layout, text='', icon='NONE', width=-1): if li > maxlines: break; layout.label(text=l, icon=icon) - icon = 'NONE' \ No newline at end of file + icon = 'NONE' -- cgit v1.2.3