diff options
author | Vilém Duha <vilda.novak@gmail.com> | 2021-04-29 18:29:30 +0300 |
---|---|---|
committer | Vilém Duha <vilda.novak@gmail.com> | 2021-04-29 18:29:30 +0300 |
commit | be10f1a6b9244d348947023ed5842eb432ca9657 (patch) | |
tree | 8e56b1d5048cf2c099edee6c7e34eb430d1e00f2 | |
parent | e670cee07525af65395a13f30f57ca5e656771a1 (diff) |
BlenderKit: fixes for right-click menu
- Ratings were not sent to server
- changed icon for complexity/work hours
- many small tweaks, finishing ratings_utils refactor
- adding various parameters to details
-
-rw-r--r-- | blenderkit/autothumb_model_bg.py | 1 | ||||
-rw-r--r-- | blenderkit/icons.py | 1 | ||||
-rw-r--r-- | blenderkit/ratings.py | 42 | ||||
-rw-r--r-- | blenderkit/ratings_utils.py | 43 | ||||
-rw-r--r-- | blenderkit/search.py | 138 | ||||
-rw-r--r-- | blenderkit/thumbnails/dumbbell.png | bin | 0 -> 1759 bytes | |||
-rw-r--r-- | blenderkit/ui.py | 2 | ||||
-rw-r--r-- | blenderkit/ui_panels.py | 98 |
8 files changed, 121 insertions, 204 deletions
diff --git a/blenderkit/autothumb_model_bg.py b/blenderkit/autothumb_model_bg.py index d8e529fd..36945b52 100644 --- a/blenderkit/autothumb_model_bg.py +++ b/blenderkit/autothumb_model_bg.py @@ -77,7 +77,6 @@ def render_thumbnails(): if __name__ == "__main__": try: - print( 'got to A') with open(BLENDERKIT_EXPORT_DATA, 'r',encoding='utf-8') as s: data = json.load(s) diff --git a/blenderkit/icons.py b/blenderkit/icons.py index ab17efcf..b8b86cba 100644 --- a/blenderkit/icons.py +++ b/blenderkit/icons.py @@ -28,6 +28,7 @@ icons_read = { 'fp.png': 'free', 'flp.png': 'full', 'trophy.png': 'trophy', + 'dumbbell.png': 'dumbbell', 'cc0.png': 'cc0', 'royalty_free.png': 'royalty_free', } diff --git a/blenderkit/ratings.py b/blenderkit/ratings.py index 118cc3ae..ae3127b1 100644 --- a/blenderkit/ratings.py +++ b/blenderkit/ratings.py @@ -50,36 +50,6 @@ def pretty_print_POST(req): )) -def upload_rating_thread(url, ratings, headers): - ''' Upload rating thread function / disconnected from blender data.''' - bk_logger.debug('upload rating ' + url + str(ratings)) - for rating_name, score in ratings: - if (score != -1 and score != 0): - rating_url = url + rating_name + '/' - data = { - "score": score, # todo this kind of mixing is too much. Should have 2 bkit structures, upload, use - } - - try: - r = rerequests.put(rating_url, data=data, verify=True, headers=headers) - - except requests.exceptions.RequestException as e: - print('ratings upload failed: %s' % str(e)) - - -def send_rating_to_thread_quality(url, ratings, headers): - '''Sens rating into thread rating, main purpose is for tasks_queue. - One function per property to avoid lost data due to stashing.''' - thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers)) - thread.start() - - -def send_rating_to_thread_work_hours(url, ratings, headers): - '''Sens rating into thread rating, main purpose is for tasks_queue. - One function per property to avoid lost data due to stashing.''' - thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers)) - thread.start() - def upload_review_thread(url, reviews, headers): r = rerequests.put(url, data=reviews, verify=True, headers=headers) @@ -103,7 +73,6 @@ def get_rating(asset_id): print(r.text) - def upload_rating(asset): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences api_key = user_preferences.api_key @@ -119,12 +88,12 @@ def upload_rating(asset): 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) + tasks_queue.add_task((ratings_utils.send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True) if bkit_ratings.rating_work_hours > 0.1: ratings = (('working_hours', round(bkit_ratings.rating_work_hours, 1)),) - tasks_queue.add_task((send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True) + tasks_queue.add_task((ratings_utils.send_rating_to_thread_work_hours, (url, ratings, headers)), wait=2.5, only_last=True) - thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers)) + thread = threading.Thread(target=ratings_utils.upload_rating_thread, args=(url, ratings, headers)) thread.start() url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/review' @@ -206,7 +175,6 @@ class UploadRatingOperator(bpy.types.Operator): return wm.invoke_props_dialog(self) - 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) @@ -387,11 +355,11 @@ class FastRateMenu(Operator): 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) + tasks_queue.add_task((ratings_utils.send_rating_to_thread_quality, (url, rtgs, headers)), wait=2.5, only_last=True) 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) + tasks_queue.add_task((ratings_utils.send_rating_to_thread_work_hours, (url, rtgs, headers)), wait=2.5, only_last=True) return {'FINISHED'} def invoke(self, context, event): diff --git a/blenderkit/ratings_utils.py b/blenderkit/ratings_utils.py index 9dc02e55..be698300 100644 --- a/blenderkit/ratings_utils.py +++ b/blenderkit/ratings_utils.py @@ -16,8 +16,46 @@ # # ##### END GPL LICENSE BLOCK ##### -#mainly update functions and callbacks for ratings properties, here to avoid circular imports. +# mainly update functions and callbacks for ratings properties, here to avoid circular imports. import bpy +from blenderkit import utils, paths, tasks_queue, rerequests +import threading +import requests +import logging + +bk_logger = logging.getLogger('blenderkit') + + +def upload_rating_thread(url, ratings, headers): + ''' Upload rating thread function / disconnected from blender data.''' + bk_logger.debug('upload rating ' + url + str(ratings)) + for rating_name, score in ratings: + if (score != -1 and score != 0): + rating_url = url + rating_name + '/' + data = { + "score": score, # todo this kind of mixing is too much. Should have 2 bkit structures, upload, use + } + + try: + r = rerequests.put(rating_url, data=data, verify=True, headers=headers) + + except requests.exceptions.RequestException as e: + print('ratings upload failed: %s' % str(e)) + + +def send_rating_to_thread_quality(url, ratings, headers): + '''Sens rating into thread rating, main purpose is for tasks_queue. + One function per property to avoid lost data due to stashing.''' + thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers)) + thread.start() + + +def send_rating_to_thread_work_hours(url, ratings, headers): + '''Sens rating into thread rating, main purpose is for tasks_queue. + One function per property to avoid lost data due to stashing.''' + thread = threading.Thread(target=upload_rating_thread, args=(url, ratings, headers)) + thread.start() + def update_ratings_quality(self, context): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences @@ -55,6 +93,7 @@ def update_ratings_work_hours(self, context): 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 @@ -118,4 +157,4 @@ def stars_enum_callback(self, context): 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 + return items diff --git a/blenderkit/search.py b/blenderkit/search.py index 3f0532b5..95b4c0cf 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -586,141 +586,10 @@ def generate_tooltip(mdata): 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 = writeblock(t, mdata['displayName'], width=col_w) # 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 = writeblockm(t, mdata, key='description', pretext='', width=col_w) + # if mdata['description'] != '': # t += '\n' return t @@ -1727,6 +1596,7 @@ class UrlOperator(Operator): def execute(self,context): bpy.ops.wm.url_open(url=self.url) + return {'FINISHED'} classes = [ diff --git a/blenderkit/thumbnails/dumbbell.png b/blenderkit/thumbnails/dumbbell.png Binary files differnew file mode 100644 index 00000000..ffc709c6 --- /dev/null +++ b/blenderkit/thumbnails/dumbbell.png diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 7ef5092a..94542834 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -1587,7 +1587,7 @@ class AssetBarOperator(bpy.types.Operator): my = event.mouse_y - r.y if event.value == 'PRESS' and mouse_in_asset_bar(mx, my): - context.window.cursor_warp(event.mouse_x-500, event.mouse_y-45); + context.window.cursor_warp(event.mouse_x-400, event.mouse_y-20); bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT') context.window.cursor_warp(event.mouse_x, event.mouse_y); diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index 4f703ba3..3ac9366a 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -1246,7 +1246,6 @@ def draw_asset_context_menu(layout, context, asset_data, from_panel=False): elif asset_data['assetBaseId'] in s['assets used'].keys() and asset_data['assetType'] != 'hdr': # HDRs are excluded from replacement, since they are always replaced. # called from asset bar: - print('context menu') op = col.operator('scene.blenderkit_download', text='Replace asset resolution') op.asset_index = ui_props.active_index @@ -1378,6 +1377,12 @@ 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) +def numeric_to_str(s): + if s: + s = str(round(s)) + else: + s = '-' + return s class AssetPopupCard(bpy.types.Operator): """Generate Cycles thumbnail for model assets""" @@ -1414,7 +1419,7 @@ class AssetPopupCard(bpy.types.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. @@ -1429,7 +1434,7 @@ class AssetPopupCard(bpy.types.Operator): 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'} ) @@ -1580,7 +1585,7 @@ class AssetPopupCard(bpy.types.Operator): icon = pcoll['royalty_free'] self.draw_property(box, - 'license:', t, + 'License:', t, icon_value=icon.icon_id, url="https://www.blenderkit.com/docs/licenses/", tooltip='All BlenderKit assets are available for commercial use. ' @@ -1618,18 +1623,38 @@ class AssetPopupCard(bpy.types.Operator): 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:') + #resolution/s + # fs = self.asset_data['files'] + # + # if fs and len(fs) > 2: + # resolutions = '' + # list.sort(fs, key=lambda f: f['fileType']) + # for f in fs: + # if f['fileType'].find('resolution') > -1: + # resolutions += f['fileType'][11:] + ' ' + # resolutions = resolutions.replace('_', '.') + # self.draw_property(box, 'Resolutions:', resolutions) + resolution = utils.get_param(self.asset_data, 'textureResolutionMax') + if resolution is not None: + ress = f"{int(round(resolution/1024,0))}K" + self.draw_property(box, 'Resolution', ress) + + self.draw_asset_parameter(box, key='designer', pretext='Designer') + self.draw_asset_parameter(box, key='manufacturer', pretext='Manufacturer')#TODO make them clickable! + self.draw_asset_parameter(box, key='designCollection', pretext='Collection') + self.draw_asset_parameter(box, key='designVariant', pretext='Variant') + 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') + # self.draw_asset_parameter(box, key='purePbr', pretext='Pure PBR') + # self.draw_asset_parameter(box, key='productionLevel', pretext='Readiness') + # self.draw_asset_parameter(box, key='condition', pretext='Condition') + # self.draw_property(box, 'Tags', self.asset_data['tags']) #TODO make them clickable! + self.draw_asset_parameter(box, key='material_style', pretext='Style') + self.draw_asset_parameter(box, key='model_style', pretext='Style') if utils.get_param(self.asset_data, 'dimensionX'): - t = '%s × %s × %s m' % (utils.fmt_length(mparams['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) @@ -1647,19 +1672,25 @@ class AssetPopupCard(bpy.types.Operator): 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 + author_box.label(text=' ') # just one extra line to give spacing 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) + author_left.template_icon(icon_value=self.gimg.preview.icon_id, scale=7) text_area = author_left.split() text_width = int(text_width * (1 - image_split)) else: @@ -1672,7 +1703,10 @@ class AssetPopupCard(bpy.types.Operator): 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!') + row = col.row() + row.enabled = False + row.label(text='Please introduce yourself to the community!') + 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' @@ -1715,26 +1749,31 @@ class AssetPopupCard(bpy.types.Operator): row = box_thumbnail.row() row.alignment = 'EXPAND' rc = self.asset_data.get('ratingsCount') - show_rating_threshold = 3 + show_rating_threshold = 5 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') + s = numeric_to_str(self.asset_data['score']) + q = numeric_to_str(self.asset_data['ratingsAverage'].get('quality')) + c = numeric_to_str(self.asset_data['ratingsAverage'].get('workingHours')) else: - box_thumbnail.label(text=f"This asset needs more ratings ( {rcount} of {show_rating_threshold} ).") + s = '-' + q = '-' + c = '-' + + pcoll = icons.icon_collections["main"] + row.label(text=str(s), icon_value=pcoll['trophy'].icon_id) + row.label(text=str(q), icon='SOLO_ON') + row.label(text=str(c), icon_value=pcoll['dumbbell'].icon_id) + + if rcount<= show_rating_threshold: + box_thumbnail.alert = True + + box_thumbnail.label(text=f"") + box_thumbnail.label(text=f"This asset has only {rcount} rating{'' if rcount == 1 else 's'} , please rate.") # box_thumbnail.label(text=f"Please rate this asset.") def draw_menu_desc_author(self, context, layout): @@ -1789,6 +1828,7 @@ class AssetPopupCard(bpy.types.Operator): asset_data = sr[ui_props.active_index] self.img = ui.get_large_thumbnail_image(asset_data) self.asset_type = asset_data['assetType'] + self.asset_id = asset_data['id'] # self.tex = utils.get_hidden_texture(self.img) # self.tex.update_tag() |