Cleanup: simplify file name incrementing logic
[blender-addons.git] / blenderkit / asset_bar_op.py
blobf0092960c9d0d4e8eb3024406f8897afb5ab75ed
1 import bpy
3 from bpy.types import Operator
5 from blenderkit.bl_ui_widgets.bl_ui_label import *
6 from blenderkit.bl_ui_widgets.bl_ui_button import *
7 # from blenderkit.bl_ui_widgets.bl_ui_checkbox import *
8 # from blenderkit.bl_ui_widgets.bl_ui_slider import *
9 # from blenderkit.bl_ui_widgets.bl_ui_up_down import *
10 from blenderkit.bl_ui_widgets.bl_ui_drag_panel import *
11 from blenderkit.bl_ui_widgets.bl_ui_draw_op import *
12 # from blenderkit.bl_ui_widgets.bl_ui_textbox import *
13 import random
14 import math
16 import blenderkit
17 from blenderkit import ui, paths, utils, search
19 from bpy.props import (
20 IntProperty,
21 BoolProperty,
22 StringProperty
26 def draw_callback_tooltip(self, context):
27 if self.draw_tooltip:
28 wm = bpy.context.window_manager
29 sr = wm.get('search results')
30 r = sr[self.active_index]
31 ui.draw_tooltip_with_author(r, 0, 500)
33 def get_area_height(self):
34 if type(self.context)!= dict:
35 self.context = self.context.copy()
36 # print(self.context)
37 if self.context.get('area') is not None:
38 return self.context['area'].height
39 # else:
40 # maxw, maxa, region = utils.get_largest_area()
41 # if maxa:
42 # self.context['area'] = maxa
43 # self.context['window'] = maxw
44 # self.context['region'] = region
45 # self.update(self.x,self.y)
47 # return self.context['area'].height
48 # print('no area found')
49 return 100
51 BL_UI_Widget.get_area_height = get_area_height
54 def asset_bar_modal(self, context, event):
55 if self._finished:
56 return {'FINISHED'}
58 if context.area:
59 context.area.tag_redraw()
60 else:
61 self.finish()
62 return {'FINISHED'}
64 if self.handle_widget_events(event):
65 return {'RUNNING_MODAL'}
67 if event.type in {"ESC"}:
68 self.finish()
70 if event.type == 'WHEELUPMOUSE':
71 self.scroll_offset -= 5
72 self.scroll_update()
73 return {'RUNNING_MODAL'}
74 elif event.type == 'WHEELDOWNMOUSE':
75 self.scroll_offset += 5
76 self.scroll_update()
77 return {'RUNNING_MODAL'}
80 return {"PASS_THROUGH"}
82 def asset_bar_invoke(self, context, event):
84 if not self.on_invoke(context, event):
85 return {"CANCELLED"}
87 args = (self, context)
89 self.register_handlers(args, context)
91 context.window_manager.modal_handler_add(self)
92 return {"RUNNING_MODAL"}
94 BL_UI_OT_draw_operator.modal = asset_bar_modal
95 BL_UI_OT_draw_operator.invoke = asset_bar_invoke
98 def set_mouse_down_right(self, mouse_down_right_func):
99 self.mouse_down_right_func = mouse_down_right_func
102 def mouse_down_right(self, x, y):
103 if self.is_in_rect(x, y):
104 self.__state = 1
105 try:
106 self.mouse_down_right_func(self)
107 except Exception as e:
108 print(e)
110 return True
112 return False
114 # def handle_event(self, event):
115 # x = event.mouse_region_x
116 # y = event.mouse_region_y
118 # if (event.type == 'LEFTMOUSE'):
119 # if (event.value == 'PRESS'):
120 # self._mouse_down = True
121 # return self.mouse_down(x, y)
122 # else:
123 # self._mouse_down = False
124 # self.mouse_up(x, y)
126 # elif (event.type == 'RIGHTMOUSE'):
127 # if (event.value == 'PRESS'):
128 # self._mouse_down_right = True
129 # return self.mouse_down_right(x, y)
130 # else:
131 # self._mouse_down_right = False
132 # self.mouse_up(x, y)
134 # elif (event.type == 'MOUSEMOVE'):
135 # self.mouse_move(x, y)
137 # inrect = self.is_in_rect(x, y)
139 # # we enter the rect
140 # if not self.__inrect and inrect:
141 # self.__inrect = True
142 # self.mouse_enter(event, x, y)
144 # # we are leaving the rect
145 # elif self.__inrect and not inrect:
146 # self.__inrect = False
147 # self.mouse_exit(event, x, y)
149 # return False
151 # elif event.value == 'PRESS' and (event.ascii != '' or event.type in self.get_input_keys()):
152 # return self.text_input(event)
154 # return False
156 BL_UI_Button.mouse_down_right = mouse_down_right
157 BL_UI_Button.set_mouse_down_right = set_mouse_down_right
158 # BL_UI_Button.handle_event = handle_event
163 class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
164 bl_idname = "view3d.blenderkit_asset_bar_widget"
165 bl_label = "BlenderKit asset bar refresh"
166 bl_description = "BlenderKit asset bar refresh"
167 bl_options = {'REGISTER'}
169 do_search: BoolProperty(name="Run Search", description='', default=True, options={'SKIP_SAVE'})
170 keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'})
171 free_only: BoolProperty(name="Free first", description='', default=False, options={'SKIP_SAVE'})
173 category: StringProperty(
174 name="Category",
175 description="search only subtree of this category",
176 default="", options={'SKIP_SAVE'})
178 tooltip: bpy.props.StringProperty(default='runs search and displays the asset bar at the same time')
180 @classmethod
181 def description(cls, context, properties):
182 return properties.tooltip
184 def new_text(self, text, x, y, width=100, height=15, text_size=None):
185 label = BL_UI_Label(x, y, width, height)
186 label.text = text
187 if text_size is None:
188 text_size = 14
189 label.text_size = text_size
190 label.text_color = self.text_color
191 return label
193 def init_tooltip(self):
194 self.tooltip_widgets = []
195 tooltip_size = 500
196 total_size = tooltip_size + 2 * self.assetbar_margin
197 self.tooltip_panel = BL_UI_Drag_Panel(0, 0, total_size, total_size)
198 self.tooltip_panel.bg_color = (0.0, 0.0, 0.0, 0.5)
199 self.tooltip_panel.visible = False
201 tooltip_image = BL_UI_Button(self.assetbar_margin, self.assetbar_margin, 1, 1)
202 tooltip_image.text = ""
203 img_path = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
204 tooltip_image.set_image(img_path)
205 tooltip_image.set_image_size((tooltip_size, tooltip_size))
206 tooltip_image.set_image_position((0, 0))
207 self.tooltip_image = tooltip_image
208 self.tooltip_widgets.append(tooltip_image)
210 bottom_panel_fraction = 0.1
211 labels_start = total_size * (1 - bottom_panel_fraction) - self.margin
213 dark_panel = BL_UI_Widget(0, labels_start, total_size, total_size * bottom_panel_fraction)
214 dark_panel.bg_color = (0.0, 0.0, 0.0, 0.7)
215 self.tooltip_widgets.append(dark_panel)
217 name_label = self.new_text('', self.assetbar_margin*2, labels_start, text_size=16)
218 self.asset_name = name_label
219 self.tooltip_widgets.append(name_label)
220 offset_y = 16 + self.margin
221 label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y,
222 text_size=14)
223 self.tooltip_widgets.append(label)
226 self.hide_tooltip()
228 def hide_tooltip(self):
229 self.tooltip_panel.visible = False
230 for w in self.tooltip_widgets:
231 w.visible = False
233 def show_tooltip(self):
234 self.tooltip_panel.visible = True
235 for w in self.tooltip_widgets:
236 w.visible = True
238 def update_ui_size(self, context):
240 if bpy.app.background or not context.area:
241 return
243 region = context.region
244 area = context.area
246 ui_props = bpy.context.scene.blenderkitUI
247 user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
248 ui_scale = bpy.context.preferences.view.ui_scale
250 self.margin = ui_props.bl_rna.properties['margin'].default * ui_scale
251 self.margin = 3
252 self.assetbar_margin = self.margin
254 self.thumb_size = user_preferences.thumb_size * ui_scale
255 self.button_size = 2 * self.margin + self.thumb_size
257 reg_multiplier = 1
258 if not bpy.context.preferences.system.use_region_overlap:
259 reg_multiplier = 0
261 for r in area.regions:
262 if r.type == 'TOOLS':
263 self.bar_x = r.width * reg_multiplier + self.margin + ui_props.bar_x_offset * ui_scale
264 elif r.type == 'UI':
265 self.bar_end = r.width * reg_multiplier + 100 * ui_scale
267 self.bar_width = region.width - ui_props.bar_x - ui_props.bar_end
269 self.wcount = math.floor(
270 (self.bar_width) / (self.button_size))
272 search_results = bpy.context.window_manager.get('search results')
273 if search_results is not None and self.wcount > 0:
274 self.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / self.wcount))
275 else:
276 self.hcount = 1
278 self.bar_height = (self.button_size) * self.hcount + 2 * self.assetbar_margin
279 # self.bar_y = region.height - ui_props.bar_y_offset * ui_scale
280 self.bar_y = ui_props.bar_y_offset * ui_scale
281 if ui_props.down_up == 'UPLOAD':
282 self.reports_y = self.bar_y - 600
283 self.reports_x = self.bar_x
284 else:
285 self.reports_y = self.bar_y - self.bar_height - 100
286 self.reports_x = self.bar_x
288 def __init__(self):
289 super().__init__()
291 self.update_ui_size(bpy.context)
293 ui_props = bpy.context.scene.blenderkitUI
295 # todo move all this to update UI size
297 self.draw_tooltip = False
298 self.scroll_offset = 0
300 self.text_color = (0.9, 0.9, 0.9, 1.0)
301 button_bg_color = (0.2, 0.2, 0.2, .1)
302 button_hover_color = (0.8, 0.8, 0.8, .2)
304 self.init_tooltip()
306 self.buttons = []
307 self.asset_buttons = []
308 self.validation_icons = []
309 self.widgets_panel = []
311 self.panel = BL_UI_Drag_Panel(0, 0, self.bar_width, self.bar_height)
312 self.panel.bg_color = (0.0, 0.0, 0.0, 0.5)
314 for a in range(0, self.wcount):
315 for b in range(0, self.hcount):
316 asset_x = self.assetbar_margin + a * (self.button_size)
317 asset_y = self.assetbar_margin + b * (self.button_size)
318 new_button = BL_UI_Button(asset_x, asset_y, self.button_size, self.button_size)
320 asset_idx = a + b * self.wcount + self.scroll_offset
321 # asset_data = sr[asset_idx]
322 # iname = blenderkit.utils.previmg_name(asset_idx)
323 # img = bpy.data.images.get(iname)
325 new_button.bg_color = button_bg_color
326 new_button.hover_bg_color = button_hover_color
327 new_button.text = "" # asset_data['name']
328 # if img:
329 # new_button.set_image(img.filepath)
331 new_button.set_image_size((self.thumb_size, self.thumb_size))
332 new_button.set_image_position((self.margin, self.margin))
333 new_button.button_index = asset_idx
334 new_button.search_index = asset_idx
335 new_button.set_mouse_down(self.drag_drop_asset)
336 new_button.set_mouse_down_right(self.asset_menu)
337 new_button.set_mouse_enter(self.enter_button)
338 new_button.set_mouse_exit(self.exit_button)
339 new_button.text_input = self.handle_key_input
340 self.asset_buttons.append(new_button)
341 # add validation icon to button
342 icon_size = 24
343 validation_icon = BL_UI_Button(asset_x + self.button_size - icon_size - self.margin,
344 asset_y + self.button_size - icon_size - self.margin, 0, 0)
346 # v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')]
347 # if v_icon is not None:
348 # img_fp = paths.get_addon_thumbnail_path(v_icon)
349 # validation_icon.set_image(img_fp)
350 validation_icon.text = ''
351 validation_icon.set_image_size((icon_size, icon_size))
352 validation_icon.set_image_position((0, 0))
353 self.validation_icons.append(validation_icon)
354 new_button.validation_icon = validation_icon
356 other_button_size = 30
358 self.button_close = BL_UI_Button(self.bar_width - other_button_size, -0, other_button_size, 15)
359 self.button_close.bg_color = button_bg_color
360 self.button_close.hover_bg_color = button_hover_color
361 self.button_close.text = "x"
362 self.button_close.set_mouse_down(self.cancel_press)
364 self.widgets_panel.append(self.button_close)
365 scroll_width = 30
366 self.button_scroll_down = BL_UI_Button(-scroll_width, 0, scroll_width, self.bar_height)
367 self.button_scroll_down.bg_color = button_bg_color
368 self.button_scroll_down.hover_bg_color = button_hover_color
369 self.button_scroll_down.text = ""
370 self.button_scroll_down.set_image(paths.get_addon_thumbnail_path('arrow_left.png'))
371 self.button_scroll_down.set_image_size((scroll_width, self.button_size))
372 self.button_scroll_down.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
374 self.button_scroll_down.set_mouse_down(self.scroll_down)
376 self.widgets_panel.append(self.button_scroll_down)
378 self.button_scroll_up = BL_UI_Button(self.bar_width, 0, scroll_width, self.bar_height)
379 self.button_scroll_up.bg_color = button_bg_color
380 self.button_scroll_up.hover_bg_color = button_hover_color
381 self.button_scroll_up.text = ""
382 self.button_scroll_up.set_image(paths.get_addon_thumbnail_path('arrow_right.png'))
383 self.button_scroll_up.set_image_size((scroll_width, self.button_size))
384 self.button_scroll_up.set_image_position((0, int((self.bar_height - self.button_size) / 2)))
386 self.button_scroll_up.set_mouse_down(self.scroll_up)
388 self.widgets_panel.append(self.button_scroll_up)
390 self.update_images()
392 def on_invoke(self, context, event):
395 if self.do_search:
396 #TODO: move the search behaviour to separate operator, since asset bar can be already woken up from a timer.
398 # we erase search keywords for cateogry search now, since these combinations usually return nothing now.
399 # when the db gets bigger, this can be deleted.
400 if self.category != '':
401 sprops = utils.get_search_props()
402 sprops.search_keywords = ''
403 search.search(category=self.category)
405 ui_props = context.scene.blenderkitUI
406 if ui_props.assetbar_on:
407 #TODO solve this otehrwise to enable more asset bars?
409 # we don't want to run the assetbar many times, that's why it has a switch on/off behaviour,
410 # unless being called with 'keep_running' prop.
411 if not self.keep_running:
412 # this sends message to the originally running operator, so it quits, and then it ends this one too.
413 # If it initiated a search, the search will finish in a thread. The switch off procedure is run
414 # by the 'original' operator, since if we get here, it means
415 # same operator is already running.
416 ui_props.turn_off = True
417 # if there was an error, we need to turn off these props so we can restart after 2 clicks
418 ui_props.assetbar_on = False
420 else:
421 pass
422 return False
424 ui_props.assetbar_on = True
426 self.active_index = -1
428 widgets_panel = self.widgets_panel
429 widgets_panel.extend(self.buttons)
430 widgets_panel.extend(self.asset_buttons)
431 widgets_panel.extend(self.validation_icons)
433 widgets = [self.panel]
435 widgets += widgets_panel
436 widgets.append(self.tooltip_panel)
437 widgets += self.tooltip_widgets
439 self.init_widgets(context, widgets)
441 self.panel.add_widgets(widgets_panel)
442 self.tooltip_panel.add_widgets(self.tooltip_widgets)
444 # Open the panel at the mouse location
445 # self.panel.set_location(bpy.context.area.width - event.mouse_x,
446 # bpy.context.area.height - event.mouse_y + 20)
447 self.panel.set_location(self.bar_x,
448 self.bar_y)
450 self.context = context
451 args = (self, context)
453 # self._handle_2d_tooltip = bpy.types.SpaceView3D.draw_handler_add(draw_callback_tooltip, args, 'WINDOW', 'POST_PIXEL')
454 return True
456 def on_finish(self, context):
457 # redraw all areas, since otherwise it stays to hang for some more time.
458 # bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d_tooltip, 'WINDOW')
460 scene = bpy.context.scene
461 ui_props = scene.blenderkitUI
462 ui_props.assetbar_on = False
464 wm = bpy.data.window_managers[0]
466 for w in wm.windows:
467 for a in w.screen.areas:
468 a.tag_redraw()
470 self._finished = True
472 # handlers
474 def enter_button(self, widget):
475 self.show_tooltip()
477 if self.active_index != widget.search_index:
478 scene = bpy.context.scene
479 wm = bpy.context.window_manager
480 sr = wm['search results']
481 asset_data = sr[widget.search_index + self.scroll_offset]
483 self.active_index = widget.search_index
484 self.draw_tooltip = True
485 self.tooltip = asset_data['tooltip']
486 ui_props = scene.blenderkitUI
487 ui_props.active_index = widget.search_index +self.scroll_offset
489 img = ui.get_large_thumbnail_image(asset_data)
490 if img:
491 self.tooltip_image.set_image(img.filepath)
492 self.asset_name.text = asset_data['name']
493 self.tooltip_panel.update(widget.x_screen + widget.width, widget.y_screen + widget.height)
494 self.tooltip_panel.layout_widgets()
496 def exit_button(self, widget):
497 # this condition checks if there wasn't another button already entered, which can happen with small button gaps
498 if self.active_index == widget.search_index:
499 scene = bpy.context.scene
500 ui_props = scene.blenderkitUI
501 ui_props.draw_tooltip = False
502 self.draw_tooltip = False
503 self.hide_tooltip()
505 def drag_drop_asset(self, widget):
506 bpy.ops.view3d.asset_drag_drop('INVOKE_DEFAULT', asset_search_index=widget.search_index + self.scroll_offset)
508 def cancel_press(self, widget):
509 self.finish()
511 def asset_menu(self, widget):
512 bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
513 # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
515 def search_more(self):
516 sro = bpy.context.window_manager.get('search results orig')
517 if sro is not None and sro.get('next') is not None:
518 blenderkit.search.search(get_next=True)
520 def update_images(self):
521 sr = bpy.context.window_manager['search results']
523 for asset_button in self.asset_buttons:
524 asset_button.asset_index = asset_button.button_index + self.scroll_offset
525 if asset_button.asset_index < len(sr):
526 asset_button.visible = True
528 asset_data = sr[asset_button.asset_index]
530 iname = blenderkit.utils.previmg_name(asset_button.asset_index)
531 # show indices for debug purposes
532 # asset_button.text = str(asset_button.asset_index)
533 img = bpy.data.images.get(iname)
534 if not img:
535 img_filepath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
536 else:
537 img_filepath = img.filepath
538 asset_button.set_image(img_filepath)
539 v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')]
540 if v_icon is not None:
541 img_fp = paths.get_addon_thumbnail_path(v_icon)
542 asset_button.validation_icon.set_image(img_fp)
543 asset_button.validation_icon.visible = True
544 else:
545 asset_button.validation_icon.visible = False
546 else:
547 asset_button.visible = False
548 asset_button.validation_icon.visible = False
550 def scroll_update(self):
551 sr = bpy.context.window_manager['search results']
552 self.scroll_offset = min(self.scroll_offset, len(sr) - (self.wcount * self.hcount))
553 self.scroll_offset = max(self.scroll_offset, 0)
554 self.update_images()
555 if len(sr) - self.scroll_offset < (self.wcount * self.hcount) + 10:
556 self.search_more()
558 def search_by_author(self, asset_index):
559 sr = bpy.context.window_manager['search results']
560 asset_data = sr[asset_index]
561 a = asset_data['author']['id']
562 if a is not None:
563 sprops = utils.get_search_props()
564 sprops.search_keywords = ''
565 sprops.search_verification_status = 'ALL'
566 utils.p('author:', a)
567 search.search(author_id=a)
568 return True
570 def handle_key_input(self, event):
571 if event.type == 'A':
572 self.search_by_author(self.active_index + self.scroll_offset)
573 return False
575 def scroll_up(self, widget):
576 self.scroll_offset += self.wcount * self.hcount
577 self.scroll_update()
579 def scroll_down(self, widget):
580 self.scroll_offset -= self.wcount * self.hcount
581 self.scroll_update()
584 def register():
585 bpy.utils.register_class(BlenderKitAssetBarOperator)
588 def unregister():
589 bpy.utils.unregister_class(BlenderKitAssetBarOperator)