Merge remote-tracking branch 'origin/blender-v2.93-release'
This commit is contained in:
commit
4fcdbfe7c2
|
@ -19,8 +19,8 @@
|
|||
bl_info = {
|
||||
"name": "BlenderKit Online Asset Library",
|
||||
"author": "Vilem Duha, Petr Dlouhy",
|
||||
"version": (2, 92, 0),
|
||||
"blender": (2, 92, 0),
|
||||
"version": (2, 93, 0),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D > Properties > BlenderKit",
|
||||
"description": "Online BlenderKit library (materials, models, brushes and more). Connects to the internet.",
|
||||
"warning": "",
|
||||
|
|
|
@ -426,8 +426,6 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
|||
json_args=args_dict,
|
||||
wait=False)
|
||||
return {'FINISHED'}
|
||||
start_thumbnailer(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
|
|
|
@ -1278,6 +1278,9 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
description="",
|
||||
default="")
|
||||
|
||||
# close_window: BoolProperty(name='Close window',
|
||||
# description='Try to close the window below mouse before download',
|
||||
# default=False)
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# return bpy.context.window_manager.BlenderKitModelThumbnails is not ''
|
||||
|
@ -1376,6 +1379,9 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
layout.prop(self, 'resolution', expand=True, icon_only=False)
|
||||
|
||||
def invoke(self, context, event):
|
||||
# if self.close_window:
|
||||
# context.window.cursor_warp(event.mouse_x-1000, event.mouse_y - 1000);
|
||||
|
||||
print(self.asset_base_id)
|
||||
wm = context.window_manager
|
||||
# only make a pop up in case of switching resolutions
|
||||
|
@ -1393,6 +1399,15 @@ class BlenderkitDownloadOperator(bpy.types.Operator):
|
|||
self.resolution = 'ORIGINAL'
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
# if self.close_window:
|
||||
# time.sleep(0.1)
|
||||
# context.area.tag_redraw()
|
||||
# time.sleep(0.1)
|
||||
#
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
def register_download():
|
||||
bpy.utils.register_class(BlenderkitDownloadOperator)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils
|
||||
from blenderkit import paths, utils, rerequests, tasks_queue, ratings_utils, icons
|
||||
|
||||
import bpy
|
||||
import requests, threading
|
||||
|
@ -176,44 +176,70 @@ class UploadRatingOperator(bpy.types.Operator):
|
|||
|
||||
|
||||
def draw_ratings_menu(self, context, layout):
|
||||
pcoll = icons.icon_collections["main"]
|
||||
|
||||
profile_name = ''
|
||||
profile = bpy.context.window_manager.get('bkit profile')
|
||||
if profile:
|
||||
profile_name = ' ' + profile['user']['firstName']
|
||||
|
||||
col = layout.column()
|
||||
# layout.template_icon_view(bkit_ratings, property, show_labels=False, scale=6.0, scale_popup=5.0)
|
||||
row = col.row()
|
||||
row.label(text='Quality:', icon = 'SOLO_ON')
|
||||
row = col.row()
|
||||
row.label(text='Please help the community by rating quality:')
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_quality_ui', expand=True, icon_only=True, emboss=False)
|
||||
if self.rating_quality>0:
|
||||
# row = col.row()
|
||||
|
||||
row.label(text=f' Thanks{profile_name}!', icon = 'FUND')
|
||||
# row.label(text=str(self.rating_quality))
|
||||
col.separator()
|
||||
col.separator()
|
||||
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.label(text='Complexity:', icon_value=pcoll['dumbbell'].icon_id)
|
||||
row = col.row()
|
||||
row.label(text=f"How many hours did this {self.asset_type} save you?")
|
||||
|
||||
if utils.profile_is_validator():
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours')
|
||||
|
||||
if self.asset_type in ('model', 'scene'):
|
||||
row = layout.row()
|
||||
if utils.profile_is_validator():
|
||||
col.prop(self, 'rating_work_hours')
|
||||
row = col.row()
|
||||
|
||||
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,
|
||||
utils.label_multiline(col,
|
||||
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()
|
||||
col.separator()
|
||||
|
||||
utils.label_multiline(layout,
|
||||
utils.label_multiline(col,
|
||||
text=f"\nThat's a lot! please be sure to give such rating only to amazing {self.asset_type}s.\n",
|
||||
width=500)
|
||||
|
||||
|
||||
elif self.asset_type == 'hdr':
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours_ui_1_10', expand=True, icon_only=False, emboss=True)
|
||||
else:
|
||||
row = layout.row()
|
||||
row = col.row()
|
||||
row.prop(self, 'rating_work_hours_ui_1_5', expand=True, icon_only=False, emboss=True)
|
||||
|
||||
if self.rating_work_hours>0:
|
||||
row = col.row()
|
||||
row.label(text=f'Thanks{profile_name}, you are amazing!', icon='FUND')
|
||||
|
||||
class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
||||
"""Rating of the assets , also directly from the asset bar - without need to download assets"""
|
||||
bl_idname = "wm.blenderkit_menu_rating_upload"
|
||||
bl_label = "Rate asset"
|
||||
bl_label = ""
|
||||
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
||||
|
||||
|
||||
|
@ -226,6 +252,7 @@ class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text=self.message)
|
||||
layout.separator()
|
||||
|
||||
draw_ratings_menu(self, context, layout)
|
||||
|
||||
|
@ -265,8 +292,9 @@ class FastRateMenu(Operator, ratings_utils.RatingsProperties):
|
|||
|
||||
if self.asset_id == '':
|
||||
return {'CANCELLED'}
|
||||
self.message = f"Rate asset {self.asset_name}"
|
||||
self.message = f"{self.asset_name}"
|
||||
wm = context.window_manager
|
||||
self.prefill_ratings()
|
||||
|
||||
if self.asset_type in ('model', 'scene'):
|
||||
# spawn a wider one for validators for the enum buttons
|
||||
|
|
|
@ -30,7 +30,6 @@ from bpy.props import (
|
|||
PointerProperty,
|
||||
)
|
||||
|
||||
|
||||
import threading
|
||||
import requests
|
||||
import logging
|
||||
|
@ -69,21 +68,43 @@ def send_rating_to_thread_work_hours(url, ratings, headers):
|
|||
thread.start()
|
||||
|
||||
|
||||
def store_rating_local(asset_id, type='quality', value=0):
|
||||
context = bpy.context
|
||||
context.window_manager['asset ratings'] = context.window_manager.get('asset ratings', {})
|
||||
context.window_manager['asset ratings'][asset_id] = context.window_manager['asset ratings'].get(asset_id, {})
|
||||
context.window_manager['asset ratings'][asset_id][type] = value
|
||||
|
||||
|
||||
def get_rating_local(asset_id):
|
||||
context = bpy.context
|
||||
context.window_manager['asset ratings'] = context.window_manager.get('asset ratings', {})
|
||||
rating = context.window_manager['asset ratings'].get(asset_id)
|
||||
if rating:
|
||||
return rating.to_dict()
|
||||
return None
|
||||
|
||||
|
||||
def update_ratings_quality(self, context):
|
||||
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
api_key = user_preferences.api_key
|
||||
|
||||
headers = utils.get_headers(api_key)
|
||||
asset = self.id_data
|
||||
if asset:
|
||||
|
||||
if not (hasattr(self, 'rating_quality')):
|
||||
# first option is for rating of assets that are from scene
|
||||
asset = self.id_data
|
||||
bkit_ratings = asset.bkit_ratings
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
asset_id = asset['asset_data']['id']
|
||||
else:
|
||||
# this part is for operator rating:
|
||||
bkit_ratings = self
|
||||
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
|
||||
asset_id = self.asset_id
|
||||
|
||||
if bkit_ratings.rating_quality > 0.1:
|
||||
url = paths.get_api_url() + f'assets/{asset_id}/rating/'
|
||||
|
||||
store_rating_local(asset_id, type='quality', value=bkit_ratings.rating_quality)
|
||||
|
||||
ratings = [('quality', bkit_ratings.rating_quality)]
|
||||
tasks_queue.add_task((send_rating_to_thread_quality, (url, ratings, headers)), wait=2.5, only_last=True)
|
||||
|
||||
|
@ -92,16 +113,21 @@ def update_ratings_work_hours(self, context):
|
|||
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
||||
api_key = user_preferences.api_key
|
||||
headers = utils.get_headers(api_key)
|
||||
asset = self.id_data
|
||||
if asset:
|
||||
if not (hasattr(self, 'rating_work_hours')):
|
||||
# first option is for rating of assets that are from scene
|
||||
asset = self.id_data
|
||||
bkit_ratings = asset.bkit_ratings
|
||||
url = paths.get_api_url() + 'assets/' + asset['asset_data']['id'] + '/rating/'
|
||||
asset_id = asset['asset_data']['id']
|
||||
else:
|
||||
# this part is for operator rating:
|
||||
bkit_ratings = self
|
||||
url = paths.get_api_url() + f'assets/{self.asset_id}/rating/'
|
||||
asset_id = self.asset_id
|
||||
|
||||
if bkit_ratings.rating_work_hours > 0.45:
|
||||
url = paths.get_api_url() + f'assets/{asset_id}/rating/'
|
||||
|
||||
store_rating_local(asset_id, type='working_hours', value=bkit_ratings.rating_work_hours)
|
||||
|
||||
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)
|
||||
|
||||
|
@ -140,8 +166,6 @@ def update_ratings_work_hours_ui_1_5(self, context):
|
|||
bpy.ops.wm.blenderkit_login('INVOKE_DEFAULT',
|
||||
message='Please login/signup to rate assets. Clicking OK takes you to web login.')
|
||||
# self.rating_work_hours_ui_1_5 = '0'
|
||||
# print('updating 1-5')
|
||||
# print(float(self.rating_work_hours_ui_1_5))
|
||||
self.rating_work_hours = float(self.rating_work_hours_ui_1_5)
|
||||
|
||||
|
||||
|
@ -171,6 +195,7 @@ def stars_enum_callback(self, context):
|
|||
items.append((f'{a + 1}', f'{a + 1}', '', icon, a + 1))
|
||||
return items
|
||||
|
||||
|
||||
class RatingsProperties():
|
||||
message: StringProperty(
|
||||
name="message",
|
||||
|
@ -279,4 +304,17 @@ class RatingsProperties():
|
|||
default='0',
|
||||
update=update_ratings_work_hours_ui_1_10,
|
||||
options={'SKIP_SAVE'}
|
||||
)
|
||||
)
|
||||
|
||||
def prefill_ratings(self):
|
||||
# pre-fill ratings
|
||||
ratings = get_rating_local(self.asset_id)
|
||||
if ratings and ratings.get('quality'):
|
||||
self.rating_quality = ratings['quality']
|
||||
if ratings and ratings.get('working_hours'):
|
||||
wh = int(ratings['working_hours'])
|
||||
self.rating_work_hours_ui = str(wh)
|
||||
if wh < 6:
|
||||
self.rating_work_hours_ui_1_5 = str(int(ratings['working_hours']))
|
||||
if wh < 11:
|
||||
self.rating_work_hours_ui_1_10 = str(int(ratings['working_hours']))
|
||||
|
|
|
@ -628,13 +628,14 @@ class ThumbDownloader(threading.Thread):
|
|||
return self._stop_event.is_set()
|
||||
|
||||
def run(self):
|
||||
print('thumb downloader', self.url)
|
||||
# print('thumb downloader', self.url)
|
||||
r = None
|
||||
try:
|
||||
r = requests.get(self.url, stream=False)
|
||||
except Exception as e:
|
||||
bk_logger.error('Thumbnail download failed')
|
||||
bk_logger.error(str(e))
|
||||
if r.status_code == 200:
|
||||
if r and r.status_code == 200:
|
||||
with open(self.path, 'wb') as f:
|
||||
f.write(r.content)
|
||||
# ORIGINALLY WE DOWNLOADED THUMBNAILS AS STREAM, BUT THIS WAS TOO SLOW.
|
||||
|
@ -1405,6 +1406,11 @@ class SearchOperator(Operator):
|
|||
options={'SKIP_SAVE'}
|
||||
)
|
||||
|
||||
# close_window: BoolProperty(name='Close window',
|
||||
# description='Try to close the window below mouse before download',
|
||||
# default=False)
|
||||
|
||||
|
||||
tooltip: bpy.props.StringProperty(default='Runs search and displays the asset bar at the same time')
|
||||
|
||||
@classmethod
|
||||
|
@ -1428,6 +1434,13 @@ class SearchOperator(Operator):
|
|||
|
||||
return {'FINISHED'}
|
||||
|
||||
# def invoke(self, context, event):
|
||||
# if self.close_window:
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y - 100);
|
||||
# context.area.tag_redraw()
|
||||
#
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
# return self. execute(context)
|
||||
|
||||
class UrlOperator(Operator):
|
||||
""""""
|
||||
|
|
|
@ -382,6 +382,7 @@ def draw_tooltip_with_author(asset_data, x, y):
|
|||
gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash'])
|
||||
|
||||
aname = asset_data['displayName']
|
||||
aname = aname[0].upper() + aname[1:]
|
||||
if len(aname)>36:
|
||||
aname = f"{aname[:33]}..."
|
||||
|
||||
|
@ -1419,10 +1420,10 @@ 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 - 400, event.mouse_y - 20);
|
||||
# context.window.cursor_warp(event.mouse_x - 300, event.mouse_y - 10);
|
||||
|
||||
bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
|
||||
context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
# context.window.cursor_warp(event.mouse_x, event.mouse_y);
|
||||
|
||||
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
|
||||
return {'RUNNING_MODAL'}
|
||||
|
|
|
@ -421,8 +421,6 @@ 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='Asset tools:')
|
||||
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
|
||||
|
@ -465,8 +463,6 @@ class NODE_PT_blenderkit_material_properties(Panel):
|
|||
if m.get('asset_data') is not None:
|
||||
ad = m['asset_data']
|
||||
layout.label(text=str(ad['name']))
|
||||
layout.label(text='Ratings:')
|
||||
draw_panel_material_ratings(self, context)
|
||||
|
||||
layout.label(text='Asset tools:')
|
||||
draw_asset_context_menu(self.layout, context, ad, from_panel=True)
|
||||
|
@ -1390,6 +1386,7 @@ def label_or_url(layout, text='', tooltip='', url='', icon_value=None, icon=None
|
|||
else:
|
||||
op = layout.operator('wm.blenderkit_tooltip', text=text)
|
||||
op.tooltip = tooltip
|
||||
# these are here to move the text to left, since operators can only center text by default
|
||||
layout.label(text='')
|
||||
layout.label(text='')
|
||||
return
|
||||
|
@ -1439,6 +1436,10 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
parameter = utils.get_param(self.asset_data, key)
|
||||
if parameter == None:
|
||||
return
|
||||
if type(parameter) == int:
|
||||
parameter = f"{parameter:,d}"
|
||||
elif type(parameter) == float:
|
||||
parameter = f"{parameter:,.1f}"
|
||||
self.draw_property(layout, pretext, parameter)
|
||||
|
||||
def draw_properties(self, layout, width=250):
|
||||
|
@ -1458,10 +1459,11 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
pcoll = icons.icon_collections["main"]
|
||||
|
||||
box = layout.box()
|
||||
|
||||
box.scale_y = 0.8
|
||||
box.label(text='Properties')
|
||||
if self.asset_data.get('license') == 'cc_zero':
|
||||
t = 'CC Zero'
|
||||
t = 'CC Zero '
|
||||
icon = pcoll['cc0']
|
||||
|
||||
else:
|
||||
|
@ -1508,29 +1510,32 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
|
||||
)
|
||||
# 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:
|
||||
fs = self.asset_data['files']
|
||||
|
||||
ress = f"{int(round(resolution / 1024, 0))}K"
|
||||
self.draw_property(box, 'Resolution', ress,
|
||||
tooltip='Maximal resolution of textures in this asset.\n' \
|
||||
'Most texture asset have also lower resolutions generated.\n' \
|
||||
'Go to BlenderKit add-on import settings to set default resolution')
|
||||
|
||||
if fs and len(fs) > 2 and utils.profile_is_validator():
|
||||
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, 'Generated:', resolutions)
|
||||
|
||||
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')
|
||||
|
@ -1544,6 +1549,14 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
utils.fmt_length(mparams['dimensionY']),
|
||||
utils.fmt_length(mparams['dimensionZ']))
|
||||
self.draw_property(box, 'Size:', t)
|
||||
if self.asset_data.get('filesSize'):
|
||||
fs = self.asset_data['filesSize']
|
||||
fsmb = fs // (1024 * 1024)
|
||||
fskb = fs % 1024
|
||||
if fsmb == 0:
|
||||
self.draw_property(box, 'Original size:', f'{fskb}KB')
|
||||
else:
|
||||
self.draw_property(box, 'Original size:', f'{fsmb}MB')
|
||||
# Tags section
|
||||
# row = box.row()
|
||||
# letters_on_row = 0
|
||||
|
@ -1597,6 +1610,10 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
icon_value=icon.icon_id,
|
||||
tooltip=plans_tooltip,
|
||||
url=plans_link)
|
||||
if utils.profile_is_validator():
|
||||
date = self.asset_data['created'][:10]
|
||||
date = f"{date[8:10]}. {date[5:7]}. {date[:4]}"
|
||||
self.draw_property(box, 'Created:', date)
|
||||
|
||||
def draw_author_area(self, context, layout, width=330):
|
||||
self.draw_author(context, layout, width=width)
|
||||
|
@ -1741,7 +1758,9 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
# top draggabe bar with name of the asset
|
||||
top_row = layout.row()
|
||||
top_drag_bar = top_row.box()
|
||||
top_drag_bar.label(text=asset_data['displayName'])
|
||||
aname = asset_data['displayName']
|
||||
aname = aname[0].upper() + aname[1:]
|
||||
top_drag_bar.label(text=aname)
|
||||
|
||||
# left side
|
||||
row = layout.row(align=True)
|
||||
|
@ -1755,8 +1774,7 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
self.draw_menu_desc_author(context, split_right, width=int(self.width * split_ratio))
|
||||
|
||||
ratings_box = layout.box()
|
||||
ratings_box.scale_y = 0.7
|
||||
ratings_box.label(text='Rate asset quality:')
|
||||
|
||||
ratings.draw_ratings_menu(self, context, ratings_box)
|
||||
tip_box = layout.box()
|
||||
tip_box.label(text=self.tip)
|
||||
|
@ -1781,6 +1799,10 @@ class AssetPopupCard(bpy.types.Operator, ratings_utils.RatingsProperties):
|
|||
bl_label = asset_data['name']
|
||||
self.tip = search.get_random_tip()
|
||||
self.tip = self.tip.replace('\n', '')
|
||||
|
||||
# pre-fill ratings
|
||||
self.prefill_ratings()
|
||||
|
||||
return wm.invoke_popup(self, width=self.width)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue