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 *
17 from blenderkit
import ui
, paths
, utils
, search
19 from bpy
.props
import (
26 def draw_callback_tooltip(self
, context
):
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()
37 if self
.context
.get('area') is not None:
38 return self
.context
['area'].height
40 # maxw, maxa, region = utils.get_largest_area()
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')
51 BL_UI_Widget
.get_area_height
= get_area_height
54 def asset_bar_modal(self
, context
, event
):
59 context
.area
.tag_redraw()
64 if self
.handle_widget_events(event
):
65 return {'RUNNING_MODAL'}
67 if event
.type in {"ESC"}:
70 if event
.type == 'WHEELUPMOUSE':
71 self
.scroll_offset
-= 5
73 return {'RUNNING_MODAL'}
74 elif event
.type == 'WHEELDOWNMOUSE':
75 self
.scroll_offset
+= 5
77 return {'RUNNING_MODAL'}
80 return {"PASS_THROUGH"}
82 def asset_bar_invoke(self
, context
, event
):
84 if not self
.on_invoke(context
, event
):
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
):
106 self
.mouse_down_right_func(self
)
107 except Exception as e
:
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)
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)
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)
151 # elif event.value == 'PRESS' and (event.ascii != '' or event.type in self.get_input_keys()):
152 # return self.text_input(event)
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(
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')
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
)
187 if text_size
is None:
189 label
.text_size
= text_size
190 label
.text_color
= self
.text_color
193 def init_tooltip(self
):
194 self
.tooltip_widgets
= []
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
,
223 self
.tooltip_widgets
.append(label
)
228 def hide_tooltip(self
):
229 self
.tooltip_panel
.visible
= False
230 for w
in self
.tooltip_widgets
:
233 def show_tooltip(self
):
234 self
.tooltip_panel
.visible
= True
235 for w
in self
.tooltip_widgets
:
238 def update_ui_size(self
, context
):
240 if bpy
.app
.background
or not context
.area
:
243 region
= context
.region
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
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
258 if not bpy
.context
.preferences
.system
.use_region_overlap
:
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
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
))
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
285 self
.reports_y
= self
.bar_y
- self
.bar_height
- 100
286 self
.reports_x
= self
.bar_x
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)
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']
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
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
)
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
)
392 def on_invoke(self
, context
, event
):
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
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
,
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')
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]
467 for a
in w
.screen
.areas
:
470 self
._finished
= True
474 def enter_button(self
, widget
):
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
)
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
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
):
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
)
535 img_filepath
= paths
.get_addon_thumbnail_path('thumbnail_notready.jpg')
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
545 asset_button
.validation_icon
.visible
= False
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)
555 if len(sr
) - self
.scroll_offset
< (self
.wcount
* self
.hcount
) + 10:
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']
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
)
570 def handle_key_input(self
, event
):
571 if event
.type == 'A':
572 self
.search_by_author(self
.active_index
+ self
.scroll_offset
)
575 def scroll_up(self
, widget
):
576 self
.scroll_offset
+= self
.wcount
* self
.hcount
579 def scroll_down(self
, widget
):
580 self
.scroll_offset
-= self
.wcount
* self
.hcount
585 bpy
.utils
.register_class(BlenderKitAssetBarOperator
)
589 bpy
.utils
.unregister_class(BlenderKitAssetBarOperator
)