From e237c47811583d92c2ad07f00591cf4499fa0ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vil=C3=A9m=20Duha?= Date: Sun, 17 Jan 2021 13:35:23 +0100 Subject: BlenderKit: fixing asset bar code This is now only included under experimental features. It includes a stripped down version of bl_ui_widngets library by Jayanam. Basically the asset bar code got split and now both asset bar codes are next to each other. --- blenderkit/__init__.py | 23 ++ blenderkit/asset_bar_op.py | 591 +++++++++++++++++++++++++++ blenderkit/bl_ui_widgets/__init__.py | 36 ++ blenderkit/bl_ui_widgets/bl_ui_button.py | 192 +++++++++ blenderkit/bl_ui_widgets/bl_ui_drag_panel.py | 58 +++ blenderkit/bl_ui_widgets/bl_ui_draw_op.py | 83 ++++ blenderkit/bl_ui_widgets/bl_ui_label.py | 57 +++ blenderkit/bl_ui_widgets/bl_ui_widget.py | 189 +++++++++ blenderkit/colors.py | 2 + blenderkit/icons.py | 6 + blenderkit/search.py | 11 +- blenderkit/ui.py | 299 +++++++++++--- blenderkit/ui_panels.py | 138 ++++++- blenderkit/utils.py | 18 +- 14 files changed, 1619 insertions(+), 84 deletions(-) create mode 100644 blenderkit/asset_bar_op.py create mode 100644 blenderkit/bl_ui_widgets/__init__.py create mode 100644 blenderkit/bl_ui_widgets/bl_ui_button.py create mode 100644 blenderkit/bl_ui_widgets/bl_ui_drag_panel.py create mode 100644 blenderkit/bl_ui_widgets/bl_ui_draw_op.py create mode 100644 blenderkit/bl_ui_widgets/bl_ui_label.py create mode 100644 blenderkit/bl_ui_widgets/bl_ui_widget.py diff --git a/blenderkit/__init__.py b/blenderkit/__init__.py index 69fcb8b6..d3cd5a72 100644 --- a/blenderkit/__init__.py +++ b/blenderkit/__init__.py @@ -34,6 +34,7 @@ if "bpy" in locals(): # modules with _bg are used for background computations in separate blender instance and that's why they don't need reload. append_link = reload(append_link) + asset_bar_op = reload(asset_bar_op) asset_inspector = reload(asset_inspector) autothumb = reload(autothumb) bg_blender = reload(bg_blender) @@ -55,8 +56,19 @@ if "bpy" in locals(): ui_panels = reload(ui_panels) upload = reload(upload) utils = reload(utils) + + bl_ui_label = reload(bl_ui_label) + bl_ui_button = reload(bl_ui_button) + # bl_ui_checkbox = reload(bl_ui_checkbox) + # bl_ui_slider = reload(bl_ui_slider) + # bl_ui_up_down = reload(bl_ui_up_down) + bl_ui_drag_panel = reload(bl_ui_drag_panel) + bl_ui_draw_op = reload(bl_ui_draw_op) + # bl_ui_textbox = reload(bl_ui_textbox) + else: from blenderkit import append_link + from blenderkit import asset_bar_op from blenderkit import asset_inspector from blenderkit import autothumb from blenderkit import bg_blender @@ -79,6 +91,15 @@ else: from blenderkit import upload from blenderkit import utils + from blenderkit.bl_ui_widgets import bl_ui_label + from blenderkit.bl_ui_widgets import bl_ui_button + # from blenderkit.bl_ui_widgets import bl_ui_checkbox + # from blenderkit.bl_ui_widgets import bl_ui_slider + # from blenderkit.bl_ui_widgets import bl_ui_up_down + from blenderkit.bl_ui_widgets import bl_ui_drag_panel + from blenderkit.bl_ui_widgets import bl_ui_draw_op + # from blenderkit.bl_ui_widgets import bl_ui_textbox + import os import math @@ -1787,6 +1808,7 @@ def register(): overrides.register_overrides() bkit_oauth.register() tasks_queue.register() + asset_bar_op.register() user_preferences = bpy.context.preferences.addons['blenderkit'].preferences if user_preferences.use_timers: @@ -1818,6 +1840,7 @@ def unregister(): overrides.unregister_overrides() bkit_oauth.unregister() tasks_queue.unregister() + asset_bar_op.unregister() del bpy.types.Scene.blenderkit_models del bpy.types.Scene.blenderkit_scene diff --git a/blenderkit/asset_bar_op.py b/blenderkit/asset_bar_op.py new file mode 100644 index 00000000..612f5885 --- /dev/null +++ b/blenderkit/asset_bar_op.py @@ -0,0 +1,591 @@ +import bpy + +from bpy.types import Operator + +from blenderkit.bl_ui_widgets.bl_ui_label import * +from blenderkit.bl_ui_widgets.bl_ui_button import * +# from blenderkit.bl_ui_widgets.bl_ui_checkbox import * +# from blenderkit.bl_ui_widgets.bl_ui_slider import * +# from blenderkit.bl_ui_widgets.bl_ui_up_down import * +from blenderkit.bl_ui_widgets.bl_ui_drag_panel import * +from blenderkit.bl_ui_widgets.bl_ui_draw_op import * +# from blenderkit.bl_ui_widgets.bl_ui_textbox import * +import random +import math + +import blenderkit +from blenderkit import ui, paths, utils, search + +from bpy.props import ( + IntProperty, + BoolProperty, + StringProperty +) + + +def draw_callback_tooltip(self, context): + if self.draw_tooltip: + s = bpy.context.scene + sr = s.get('search results') + r = sr[self.active_index] + ui.draw_tooltip_with_author(r, 0, 500) + +def get_area_height(self): + if type(self.context)!= dict: + self.context = self.context.copy() + # print(self.context) + if self.context.get('area') is not None: + return self.context['area'].height + # else: + # maxw, maxa, region = utils.get_largest_area() + # if maxa: + # self.context['area'] = maxa + # self.context['window'] = maxw + # self.context['region'] = region + # self.update(self.x,self.y) + # + # return self.context['area'].height + # print('no area found') + return 100 + +BL_UI_Widget.get_area_height = get_area_height + + +def asset_bar_modal(self, context, event): + if self._finished: + return {'FINISHED'} + + if context.area: + context.area.tag_redraw() + else: + self.finish() + return {'FINISHED'} + + if self.handle_widget_events(event): + return {'RUNNING_MODAL'} + + if event.type in {"ESC"}: + self.finish() + + if event.type == 'WHEELUPMOUSE': + self.scroll_offset -= 5 + self.scroll_update() + return {'RUNNING_MODAL'} + elif event.type == 'WHEELDOWNMOUSE': + self.scroll_offset += 5 + self.scroll_update() + return {'RUNNING_MODAL'} + + + return {"PASS_THROUGH"} + +def asset_bar_invoke(self, context, event): + + if not self.on_invoke(context, event): + return {"CANCELLED"} + + args = (self, context) + + self.register_handlers(args, context) + + context.window_manager.modal_handler_add(self) + return {"RUNNING_MODAL"} + +BL_UI_OT_draw_operator.modal = asset_bar_modal +BL_UI_OT_draw_operator.invoke = asset_bar_invoke + + +def set_mouse_down_right(self, mouse_down_right_func): + self.mouse_down_right_func = mouse_down_right_func + + +def mouse_down_right(self, x, y): + if self.is_in_rect(x, y): + self.__state = 1 + try: + self.mouse_down_right_func(self) + except Exception as e: + print(e) + + return True + + return False + +# def handle_event(self, event): +# x = event.mouse_region_x +# y = event.mouse_region_y +# +# if (event.type == 'LEFTMOUSE'): +# if (event.value == 'PRESS'): +# self._mouse_down = True +# return self.mouse_down(x, y) +# else: +# self._mouse_down = False +# self.mouse_up(x, y) +# +# elif (event.type == 'RIGHTMOUSE'): +# if (event.value == 'PRESS'): +# self._mouse_down_right = True +# return self.mouse_down_right(x, y) +# else: +# self._mouse_down_right = False +# self.mouse_up(x, y) +# +# elif (event.type == 'MOUSEMOVE'): +# self.mouse_move(x, y) +# +# inrect = self.is_in_rect(x, y) +# +# # we enter the rect +# if not self.__inrect and inrect: +# self.__inrect = True +# self.mouse_enter(event, x, y) +# +# # we are leaving the rect +# elif self.__inrect and not inrect: +# self.__inrect = False +# self.mouse_exit(event, x, y) +# +# return False +# +# elif event.value == 'PRESS' and (event.ascii != '' or event.type in self.get_input_keys()): +# return self.text_input(event) +# +# return False + +BL_UI_Button.mouse_down_right = mouse_down_right +BL_UI_Button.set_mouse_down_right = set_mouse_down_right +# BL_UI_Button.handle_event = handle_event + + + + +class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator): + bl_idname = "view3d.blenderkit_asset_bar_widget" + bl_label = "BlenderKit asset bar refresh" + bl_description = "BlenderKit asset bar refresh" + bl_options = {'REGISTER'} + + do_search: BoolProperty(name="Run Search", description='', default=True, options={'SKIP_SAVE'}) + keep_running: BoolProperty(name="Keep Running", description='', default=True, options={'SKIP_SAVE'}) + free_only: BoolProperty(name="Free first", description='', default=False, options={'SKIP_SAVE'}) + + category: StringProperty( + name="Category", + description="search only subtree of this category", + default="", options={'SKIP_SAVE'}) + + tooltip: bpy.props.StringProperty(default='runs search and displays the asset bar at the same time') + + @classmethod + def description(cls, context, properties): + return properties.tooltip + + def new_text(self, text, x, y, width=100, height=15, text_size=None): + label = BL_UI_Label(x, y, width, height) + label.text = text + if text_size is None: + text_size = 14 + label.text_size = text_size + label.text_color = self.text_color + return label + + def init_tooltip(self): + self.tooltip_widgets = [] + tooltip_size = 500 + total_size = tooltip_size + 2 * self.assetbar_margin + self.tooltip_panel = BL_UI_Drag_Panel(0, 0, total_size, total_size) + self.tooltip_panel.bg_color = (0.0, 0.0, 0.0, 0.5) + self.tooltip_panel.visible = False + + tooltip_image = BL_UI_Button(self.assetbar_margin, self.assetbar_margin, 1, 1) + tooltip_image.text = "" + img_path = paths.get_addon_thumbnail_path('thumbnail_notready.jpg') + tooltip_image.set_image(img_path) + tooltip_image.set_image_size((tooltip_size, tooltip_size)) + tooltip_image.set_image_position((0, 0)) + self.tooltip_image = tooltip_image + self.tooltip_widgets.append(tooltip_image) + + bottom_panel_fraction = 0.1 + labels_start = total_size * (1 - bottom_panel_fraction) - self.margin + + dark_panel = BL_UI_Widget(0, labels_start, total_size, total_size * bottom_panel_fraction) + dark_panel.bg_color = (0.0, 0.0, 0.0, 0.7) + self.tooltip_widgets.append(dark_panel) + + name_label = self.new_text('', self.assetbar_margin*2, labels_start, text_size=16) + self.asset_name = name_label + self.tooltip_widgets.append(name_label) + offset_y = 16 + self.margin + label = self.new_text('Left click or drag to append/link. Right click for more options.', self.assetbar_margin*2, labels_start + offset_y, + text_size=14) + self.tooltip_widgets.append(label) + + + self.hide_tooltip() + + def hide_tooltip(self): + self.tooltip_panel.visible = False + for w in self.tooltip_widgets: + w.visible = False + + def show_tooltip(self): + self.tooltip_panel.visible = True + for w in self.tooltip_widgets: + w.visible = True + + def update_ui_size(self, context): + + if bpy.app.background or not context.area: + return + + region = context.region + area = context.area + + ui_props = bpy.context.scene.blenderkitUI + user_preferences = bpy.context.preferences.addons['blenderkit'].preferences + ui_scale = bpy.context.preferences.view.ui_scale + + self.margin = ui_props.bl_rna.properties['margin'].default * ui_scale + self.margin = 3 + self.assetbar_margin = self.margin + + self.thumb_size = user_preferences.thumb_size * ui_scale + self.button_size = 2 * self.margin + self.thumb_size + + reg_multiplier = 1 + if not bpy.context.preferences.system.use_region_overlap: + reg_multiplier = 0 + + for r in area.regions: + if r.type == 'TOOLS': + self.bar_x = r.width * reg_multiplier + self.margin + ui_props.bar_x_offset * ui_scale + elif r.type == 'UI': + self.bar_end = r.width * reg_multiplier + 100 * ui_scale + + self.bar_width = region.width - ui_props.bar_x - ui_props.bar_end + + self.wcount = math.floor( + (self.bar_width) / (self.button_size)) + + search_results = bpy.context.scene.get('search results') + if search_results is not None and self.wcount > 0: + self.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / self.wcount)) + else: + self.hcount = 1 + + self.bar_height = (self.button_size) * self.hcount + 2 * self.assetbar_margin + # self.bar_y = region.height - ui_props.bar_y_offset * ui_scale + self.bar_y = ui_props.bar_y_offset * ui_scale + if ui_props.down_up == 'UPLOAD': + self.reports_y = self.bar_y - 600 + self.reports_x = self.bar_x + else: + self.reports_y = self.bar_y - self.bar_height - 100 + self.reports_x = self.bar_x + + def __init__(self): + super().__init__() + + self.update_ui_size(bpy.context) + + ui_props = bpy.context.scene.blenderkitUI + + # todo move all this to update UI size + + self.draw_tooltip = False + self.scroll_offset = 0 + + self.text_color = (0.9, 0.9, 0.9, 1.0) + button_bg_color = (0.2, 0.2, 0.2, .1) + button_hover_color = (0.8, 0.8, 0.8, .2) + + self.init_tooltip() + + self.buttons = [] + self.asset_buttons = [] + self.validation_icons = [] + self.widgets_panel = [] + + self.panel = BL_UI_Drag_Panel(0, 0, self.bar_width, self.bar_height) + self.panel.bg_color = (0.0, 0.0, 0.0, 0.5) + + sr = bpy.context.scene['search results'] + + for a in range(0, self.wcount): + for b in range(0, self.hcount): + asset_x = self.assetbar_margin + a * (self.button_size) + asset_y = self.assetbar_margin + b * (self.button_size) + new_button = BL_UI_Button(asset_x, asset_y, self.button_size, self.button_size) + + asset_idx = a + b * self.wcount + self.scroll_offset + # asset_data = sr[asset_idx] + # iname = blenderkit.utils.previmg_name(asset_idx) + # img = bpy.data.images.get(iname) + + new_button.bg_color = button_bg_color + new_button.hover_bg_color = button_hover_color + new_button.text = "" # asset_data['name'] + # if img: + # new_button.set_image(img.filepath) + + new_button.set_image_size((self.thumb_size, self.thumb_size)) + new_button.set_image_position((self.margin, self.margin)) + new_button.button_index = asset_idx + new_button.search_index = asset_idx + new_button.set_mouse_down(self.drag_drop_asset) + new_button.set_mouse_down_right(self.asset_menu) + new_button.set_mouse_enter(self.enter_button) + new_button.set_mouse_exit(self.exit_button) + new_button.text_input = self.handle_key_input + self.asset_buttons.append(new_button) + # add validation icon to button + icon_size = 24 + validation_icon = BL_UI_Button(asset_x + self.button_size - icon_size - self.margin, + asset_y + self.button_size - icon_size - self.margin, 0, 0) + + # v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')] + # if v_icon is not None: + # img_fp = paths.get_addon_thumbnail_path(v_icon) + # validation_icon.set_image(img_fp) + validation_icon.text = '' + validation_icon.set_image_size((icon_size, icon_size)) + validation_icon.set_image_position((0, 0)) + self.validation_icons.append(validation_icon) + new_button.validation_icon = validation_icon + + other_button_size = 30 + + self.button_close = BL_UI_Button(self.bar_width - other_button_size, -0, other_button_size, 15) + self.button_close.bg_color = button_bg_color + self.button_close.hover_bg_color = button_hover_color + self.button_close.text = "x" + self.button_close.set_mouse_down(self.cancel_press) + + self.widgets_panel.append(self.button_close) + scroll_width = 30 + self.button_scroll_down = BL_UI_Button(-scroll_width, 0, scroll_width, self.bar_height) + self.button_scroll_down.bg_color = button_bg_color + self.button_scroll_down.hover_bg_color = button_hover_color + self.button_scroll_down.text = "" + self.button_scroll_down.set_image(paths.get_addon_thumbnail_path('arrow_left.png')) + self.button_scroll_down.set_image_size((scroll_width, self.button_size)) + self.button_scroll_down.set_image_position((0, int((self.bar_height - self.button_size) / 2))) + + self.button_scroll_down.set_mouse_down(self.scroll_down) + + self.widgets_panel.append(self.button_scroll_down) + + self.button_scroll_up = BL_UI_Button(self.bar_width, 0, scroll_width, self.bar_height) + self.button_scroll_up.bg_color = button_bg_color + self.button_scroll_up.hover_bg_color = button_hover_color + self.button_scroll_up.text = "" + self.button_scroll_up.set_image(paths.get_addon_thumbnail_path('arrow_right.png')) + self.button_scroll_up.set_image_size((scroll_width, self.button_size)) + self.button_scroll_up.set_image_position((0, int((self.bar_height - self.button_size) / 2))) + + self.button_scroll_up.set_mouse_down(self.scroll_up) + + self.widgets_panel.append(self.button_scroll_up) + + self.update_images() + + def on_invoke(self, context, event): + + + if self.do_search: + #TODO: move the search behaviour to separate operator, since asset bar can be already woken up from a timer. + + # we erase search keywords for cateogry search now, since these combinations usually return nothing now. + # when the db gets bigger, this can be deleted. + if self.category != '': + sprops = utils.get_search_props() + sprops.search_keywords = '' + search.search(category=self.category) + + ui_props = context.scene.blenderkitUI + if ui_props.assetbar_on: + #TODO solve this otehrwise to enable more asset bars? + + # we don't want to run the assetbar many times, that's why it has a switch on/off behaviour, + # unless being called with 'keep_running' prop. + if not self.keep_running: + # this sends message to the originally running operator, so it quits, and then it ends this one too. + # If it initiated a search, the search will finish in a thread. The switch off procedure is run + # by the 'original' operator, since if we get here, it means + # same operator is already running. + ui_props.turn_off = True + # if there was an error, we need to turn off these props so we can restart after 2 clicks + ui_props.assetbar_on = False + + else: + pass + return False + + ui_props.assetbar_on = True + + self.active_index = -1 + + widgets_panel = self.widgets_panel + widgets_panel.extend(self.buttons) + widgets_panel.extend(self.asset_buttons) + widgets_panel.extend(self.validation_icons) + + widgets = [self.panel] + + widgets += widgets_panel + widgets.append(self.tooltip_panel) + widgets += self.tooltip_widgets + + self.init_widgets(context, widgets) + + self.panel.add_widgets(widgets_panel) + self.tooltip_panel.add_widgets(self.tooltip_widgets) + + # Open the panel at the mouse location + # self.panel.set_location(bpy.context.area.width - event.mouse_x, + # bpy.context.area.height - event.mouse_y + 20) + self.panel.set_location(self.bar_x, + self.bar_y) + + self.context = context + args = (self, context) + + # self._handle_2d_tooltip = bpy.types.SpaceView3D.draw_handler_add(draw_callback_tooltip, args, 'WINDOW', 'POST_PIXEL') + return True + + def on_finish(self, context): + # redraw all areas, since otherwise it stays to hang for some more time. + # bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d_tooltip, 'WINDOW') + + scene = bpy.context.scene + ui_props = scene.blenderkitUI + ui_props.assetbar_on = False + + wm = bpy.data.window_managers[0] + + for w in wm.windows: + for a in w.screen.areas: + a.tag_redraw() + + self._finished = True + + # handlers + + def enter_button(self, widget): + self.show_tooltip() + + if self.active_index != widget.search_index: + scene = bpy.context.scene + sr = scene['search results'] + asset_data = sr[widget.search_index + self.scroll_offset] + + self.active_index = widget.search_index + self.draw_tooltip = True + self.tooltip = asset_data['tooltip'] + ui_props = scene.blenderkitUI + ui_props.active_index = widget.search_index +self.scroll_offset + + img = ui.get_large_thumbnail_image(asset_data) + if img: + self.tooltip_image.set_image(img.filepath) + self.asset_name.text = asset_data['name'] + self.tooltip_panel.update(widget.x_screen + widget.width, widget.y_screen + widget.height) + self.tooltip_panel.layout_widgets() + + def exit_button(self, widget): + # this condition checks if there wasn't another button already entered, which can happen with small button gaps + if self.active_index == widget.search_index: + scene = bpy.context.scene + ui_props = scene.blenderkitUI + ui_props.draw_tooltip = False + self.draw_tooltip = False + self.hide_tooltip() + + def drag_drop_asset(self, widget): + bpy.ops.view3d.asset_drag_drop('INVOKE_DEFAULT', asset_search_index=widget.search_index + self.scroll_offset) + + def cancel_press(self, widget): + self.finish() + + def asset_menu(self, widget): + bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT') + # bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu') + + def search_more(self): + sro = bpy.context.scene.get('search results orig') + if sro is not None and sro.get('next') is not None: + blenderkit.search.search(get_next=True) + def update_images(self): + sr = bpy.context.scene['search results'] + + for asset_button in self.asset_buttons: + asset_button.asset_index = asset_button.button_index + self.scroll_offset + if asset_button.asset_index < len(sr): + asset_button.visible = True + + asset_data = sr[asset_button.asset_index] + + iname = blenderkit.utils.previmg_name(asset_button.asset_index) + # show indices for debug purposes + # asset_button.text = str(asset_button.asset_index) + img = bpy.data.images.get(iname) + if not img: + img_filepath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg') + else: + img_filepath = img.filepath + asset_button.set_image(img_filepath) + v_icon = ui.verification_icons[asset_data.get('verificationStatus', 'validated')] + if v_icon is not None: + img_fp = paths.get_addon_thumbnail_path(v_icon) + asset_button.validation_icon.set_image(img_fp) + asset_button.validation_icon.visible = True + else: + asset_button.validation_icon.visible = False + else: + asset_button.visible = False + asset_button.validation_icon.visible = False + + def scroll_update(self): + sr = bpy.context.scene['search results'] + self.scroll_offset = min(self.scroll_offset, len(sr) - (self.wcount * self.hcount)) + self.scroll_offset = max(self.scroll_offset, 0) + self.update_images() + if len(sr) - self.scroll_offset < (self.wcount * self.hcount) + 10: + self.search_more() + + def search_by_author(self, asset_index): + sr = bpy.context.scene['search results'] + asset_data = sr[asset_index] + a = asset_data['author']['id'] + if a is not None: + sprops = utils.get_search_props() + sprops.search_keywords = '' + sprops.search_verification_status = 'ALL' + utils.p('author:', a) + search.search(author_id=a) + return True + + def handle_key_input(self, event): + if event.type == 'A': + self.search_by_author(self.active_index + self.scroll_offset) + return False + + def scroll_up(self, widget): + sr = bpy.context.scene['search results'] + self.scroll_offset += self.wcount * self.hcount + self.scroll_update() + + def scroll_down(self, widget): + sr = bpy.context.scene['search results'] + self.scroll_offset -= self.wcount * self.hcount + self.scroll_update() + + +def register(): + bpy.utils.register_class(BlenderKitAssetBarOperator) + + +def unregister(): + bpy.utils.unregister_class(BlenderKitAssetBarOperator) diff --git a/blenderkit/bl_ui_widgets/__init__.py b/blenderkit/bl_ui_widgets/__init__.py new file mode 100644 index 00000000..745f59c9 --- /dev/null +++ b/blenderkit/bl_ui_widgets/__init__.py @@ -0,0 +1,36 @@ +bl_info = { + "name": "BL UI Widgets", + "description": "UI Widgets to draw in the 3D view", + "author": "Jayanam", + "version": (0, 6, 4, 2), + "blender": (2, 80, 0), + "location": "View3D", + "category": "Object"} + +# Blender imports +import bpy + +from bpy.props import * + + +addon_keymaps = [] + +def register(): + + bpy.utils.register_class(DP_OT_draw_operator) + kcfg = bpy.context.window_manager.keyconfigs.addon + if kcfg: + km = kcfg.keymaps.new(name='3D View', space_type='VIEW_3D') + + + addon_keymaps.append((km, kmi)) + +def unregister(): + for km, kmi in addon_keymaps: + km.keymap_items.remove(kmi) + addon_keymaps.clear() + + bpy.utils.unregister_class(DP_OT_draw_operator) + +if __name__ == "__main__": + register() diff --git a/blenderkit/bl_ui_widgets/bl_ui_button.py b/blenderkit/bl_ui_widgets/bl_ui_button.py new file mode 100644 index 00000000..ce26c54e --- /dev/null +++ b/blenderkit/bl_ui_widgets/bl_ui_button.py @@ -0,0 +1,192 @@ +from . bl_ui_widget import * + +import blf +import bpy + +class BL_UI_Button(BL_UI_Widget): + + def __init__(self, x, y, width, height): + super().__init__(x, y, width, height) + self._text_color = (1.0, 1.0, 1.0, 1.0) + self._hover_bg_color = (0.5, 0.5, 0.5, 1.0) + self._select_bg_color = (0.7, 0.7, 0.7, 1.0) + + self._text = "Button" + self._text_size = 16 + self._textpos = (x, y) + + self.__state = 0 + self.__image = None + self.__image_size = (24, 24) + self.__image_position = (4, 2) + + @property + def text_color(self): + return self._text_color + + @text_color.setter + def text_color(self, value): + self._text_color = value + + @property + def text(self): + return self._text + + @text.setter + def text(self, value): + self._text = value + + @property + def text_size(self): + return self._text_size + + @text_size.setter + def text_size(self, value): + self._text_size = value + + @property + def hover_bg_color(self): + return self._hover_bg_color + + @hover_bg_color.setter + def hover_bg_color(self, value): + self._hover_bg_color = value + + @property + def select_bg_color(self): + return self._select_bg_color + + @select_bg_color.setter + def select_bg_color(self, value): + self._select_bg_color = value + + def set_image_size(self, imgage_size): + self.__image_size = imgage_size + + def set_image_position(self, image_position): + self.__image_position = image_position + + def set_image(self, rel_filepath): + try: + self.__image = bpy.data.images.load(rel_filepath, check_existing=True) + self.__image.gl_load() + except: + pass + + def update(self, x, y): + super().update(x, y) + self._textpos = [x, y] + + def draw(self): + if not self.visible: + return + + area_height = self.get_area_height() + + self.shader.bind() + + self.set_colors() + + bgl.glEnable(bgl.GL_BLEND) + + self.batch_panel.draw(self.shader) + + self.draw_image() + + bgl.glDisable(bgl.GL_BLEND) + + # Draw text + self.draw_text(area_height) + + def set_colors(self): + color = self._bg_color + text_color = self._text_color + + # pressed + if self.__state == 1: + color = self._select_bg_color + + # hover + elif self.__state == 2: + color = self._hover_bg_color + + self.shader.uniform_float("color", color) + + def draw_text(self, area_height): + blf.size(0, self._text_size, 72) + size = blf.dimensions(0, self._text) + + textpos_y = area_height - self._textpos[1] - (self.height + size[1]) / 2.0 + blf.position(0, self._textpos[0] + (self.width - size[0]) / 2.0, textpos_y + 1, 0) + + r, g, b, a = self._text_color + blf.color(0, r, g, b, a) + + blf.draw(0, self._text) + + def draw_image(self): + if self.__image is not None: + try: + y_screen_flip = self.get_area_height() - self.y_screen + + off_x, off_y = self.__image_position + sx, sy = self.__image_size + + # bottom left, top left, top right, bottom right + vertices = ( + (self.x_screen + off_x, y_screen_flip - off_y), + (self.x_screen + off_x, y_screen_flip - sy - off_y), + (self.x_screen + off_x + sx, y_screen_flip - sy - off_y), + (self.x_screen + off_x + sx, y_screen_flip - off_y)) + + self.shader_img = gpu.shader.from_builtin('2D_IMAGE') + self.batch_img = batch_for_shader(self.shader_img, 'TRI_FAN', + { "pos" : vertices, + "texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)) + },) + + # send image to gpu if it isn't there already + if self.__image.gl_load(): + raise Exception() + + bgl.glActiveTexture(bgl.GL_TEXTURE0) + bgl.glBindTexture(bgl.GL_TEXTURE_2D, self.__image.bindcode) + + self.shader_img.bind() + self.shader_img.uniform_int("image", 0) + self.batch_img.draw(self.shader_img) + return True + except: + pass + + return False + + def set_mouse_down(self, mouse_down_func): + self.mouse_down_func = mouse_down_func + + def mouse_down(self, x, y): + if self.is_in_rect(x,y): + self.__state = 1 + try: + self.mouse_down_func(self) + except Exception as e: + print(e) + + return True + + return False + + def mouse_move(self, x, y): + if self.is_in_rect(x,y): + if(self.__state != 1): + + # hover state + self.__state = 2 + else: + self.__state = 0 + + def mouse_up(self, x, y): + if self.is_in_rect(x,y): + self.__state = 2 + else: + self.__state = 0 \ No newline at end of file diff --git a/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py b/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py new file mode 100644 index 00000000..e07aa784 --- /dev/null +++ b/blenderkit/bl_ui_widgets/bl_ui_drag_panel.py @@ -0,0 +1,58 @@ +from . bl_ui_widget import * + +class BL_UI_Drag_Panel(BL_UI_Widget): + + def __init__(self, x, y, width, height): + super().__init__(x,y, width, height) + self.drag_offset_x = 0 + self.drag_offset_y = 0 + self.is_drag = False + self.widgets = [] + + def set_location(self, x, y): + super().set_location(x,y) + self.layout_widgets() + + def add_widget(self, widget): + self.widgets.append(widget) + + def add_widgets(self, widgets): + self.widgets = widgets + self.layout_widgets() + + def layout_widgets(self): + for widget in self.widgets: + widget.update(self.x_screen + widget.x, self.y_screen + widget.y) + + def update(self, x, y): + super().update(x - self.drag_offset_x, y + self.drag_offset_y) + + def child_widget_focused(self, x, y): + for widget in self.widgets: + if widget.is_in_rect(x, y): + return True + return False + + def mouse_down(self, x, y): + if self.child_widget_focused(x, y): + return False + + if self.is_in_rect(x,y): + height = self.get_area_height() + self.is_drag = True + self.drag_offset_x = x - self.x_screen + self.drag_offset_y = y - (height - self.y_screen) + return True + + return False + + def mouse_move(self, x, y): + if self.is_drag: + height = self.get_area_height() + self.update(x, height - y) + self.layout_widgets() + + def mouse_up(self, x, y): + self.is_drag = False + self.drag_offset_x = 0 + self.drag_offset_y = 0 \ No newline at end of file diff --git a/blenderkit/bl_ui_widgets/bl_ui_draw_op.py b/blenderkit/bl_ui_widgets/bl_ui_draw_op.py new file mode 100644 index 00000000..2f534479 --- /dev/null +++ b/blenderkit/bl_ui_widgets/bl_ui_draw_op.py @@ -0,0 +1,83 @@ +import bpy + +from bpy.types import Operator + +class BL_UI_OT_draw_operator(Operator): + bl_idname = "object.bl_ui_ot_draw_operator" + bl_label = "bl ui widgets operator" + bl_description = "Operator for bl ui widgets" + bl_options = {'REGISTER'} + + def __init__(self): + self.draw_handle = None + self.draw_event = None + self._finished = False + + self.widgets = [] + + def init_widgets(self, context, widgets): + self.widgets = widgets + for widget in self.widgets: + widget.init(context) + + def on_invoke(self, context, event): + pass + + def on_finish(self, context): + self._finished = True + + def invoke(self, context, event): + + self.on_invoke(context, event) + + args = (self, context) + + self.register_handlers(args, context) + + context.window_manager.modal_handler_add(self) + return {"RUNNING_MODAL"} + + def register_handlers(self, args, context): + self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_callback_px, args, "WINDOW", "POST_PIXEL") + self.draw_event = context.window_manager.event_timer_add(0.1, window=context.window) + + def unregister_handlers(self, context): + + context.window_manager.event_timer_remove(self.draw_event) + + bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, "WINDOW") + + self.draw_handle = None + self.draw_event = None + + def handle_widget_events(self, event): + result = False + for widget in self.widgets: + if widget.handle_event(event): + result = True + return result + + def modal(self, context, event): + + if self._finished: + return {'FINISHED'} + + if context.area: + context.area.tag_redraw() + + if self.handle_widget_events(event): + return {'RUNNING_MODAL'} + + if event.type in {"ESC"}: + self.finish() + + return {"PASS_THROUGH"} + + def finish(self): + self.unregister_handlers(bpy.context) + self.on_finish(bpy.context) + + # Draw handler to paint onto the screen + def draw_callback_px(self, op, context): + for widget in self.widgets: + widget.draw() \ No newline at end of file diff --git a/blenderkit/bl_ui_widgets/bl_ui_label.py b/blenderkit/bl_ui_widgets/bl_ui_label.py new file mode 100644 index 00000000..11743b41 --- /dev/null +++ b/blenderkit/bl_ui_widgets/bl_ui_label.py @@ -0,0 +1,57 @@ +from . bl_ui_widget import * + +import blf + +class BL_UI_Label(BL_UI_Widget): + + def __init__(self, x, y, width, height): + super().__init__(x, y, width, height) + + self._text_color = (1.0, 1.0, 1.0, 1.0) + self._text = "Label" + self._text_size = 16 + + @property + def text_color(self): + return self._text_color + + @text_color.setter + def text_color(self, value): + self._text_color = value + + @property + def text(self): + return self._text + + @text.setter + def text(self, value): + self._text = value + + @property + def text_size(self): + return self._text_size + + @text_size.setter + def text_size(self, value): + self._text_size = value + + def is_in_rect(self, x, y): + return False + + def draw(self): + if not self.visible: + return + + area_height = self.get_area_height() + + blf.size(0, self._text_size, 72) + size = blf.dimensions(0, self._text) + + textpos_y = area_height - self.y_screen - self.height + blf.position(0, self.x_screen, textpos_y, 0) + + r, g, b, a = self._text_color + + blf.color(0, r, g, b, a) + + blf.draw(0, self._text) \ No newline at end of file diff --git a/blenderkit/bl_ui_widgets/bl_ui_widget.py b/blenderkit/bl_ui_widgets/bl_ui_widget.py new file mode 100644 index 00000000..2ae529fa --- /dev/null +++ b/blenderkit/bl_ui_widgets/bl_ui_widget.py @@ -0,0 +1,189 @@ +import gpu +import bgl + +from gpu_extras.batch import batch_for_shader + +class BL_UI_Widget: + + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.x_screen = x + self.y_screen = y + self.width = width + self.height = height + self._bg_color = (0.8, 0.8, 0.8, 1.0) + self._tag = None + self.context = None + self.__inrect = False + self._mouse_down = False + self._mouse_down_right = False + self._is_visible = True + + def set_location(self, x, y): + self.x = x + self.y = y + self.x_screen = x + self.y_screen = y + self.update(x,y) + + @property + def bg_color(self): + return self._bg_color + + @bg_color.setter + def bg_color(self, value): + self._bg_color = value + + @property + def visible(self): + return self._is_visible + + @visible.setter + def visible(self, value): + self._is_visible = value + + @property + def tag(self): + return self._tag + + @tag.setter + def tag(self, value): + self._tag = value + + def draw(self): + if not self.visible: + return + + self.shader.bind() + self.shader.uniform_float("color", self._bg_color) + + bgl.glEnable(bgl.GL_BLEND) + self.batch_panel.draw(self.shader) + bgl.glDisable(bgl.GL_BLEND) + + def init(self, context): + self.context = context + self.update(self.x, self.y) + + def update(self, x, y): + + area_height = self.get_area_height() + + self.x_screen = x + self.y_screen = y + + indices = ((0, 1, 2), (0, 2, 3)) + + y_screen_flip = area_height - self.y_screen + + # bottom left, top left, top right, bottom right + vertices = ( + (self.x_screen, y_screen_flip), + (self.x_screen, y_screen_flip - self.height), + (self.x_screen + self.width, y_screen_flip - self.height), + (self.x_screen + self.width, y_screen_flip)) + + self.shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR') + self.batch_panel = batch_for_shader(self.shader, 'TRIS', {"pos" : vertices}, indices=indices) + + def handle_event(self, event): + x = event.mouse_region_x + y = event.mouse_region_y + + if (event.type == 'LEFTMOUSE'): + if (event.value == 'PRESS'): + self._mouse_down = True + return self.mouse_down(x, y) + else: + self._mouse_down = False + self.mouse_up(x, y) + + elif (event.type == 'RIGHTMOUSE'): + if (event.value == 'PRESS'): + self._mouse_down_right = True + return self.mouse_down_right(x, y) + else: + self._mouse_down_right = False + self.mouse_up(x, y) + + elif (event.type == 'MOUSEMOVE'): + self.mouse_move(x, y) + + inrect = self.is_in_rect(x, y) + + # we enter the rect + if not self.__inrect and inrect: + self.__inrect = True + self.mouse_enter(event, x, y) + + # we are leaving the rect + elif self.__inrect and not inrect: + self.__inrect = False + self.mouse_exit(event, x, y) + + return False + + elif event.value == 'PRESS' and (event.ascii != '' or event.type in self.get_input_keys()): + return self.text_input(event) + + return False + + def get_input_keys(self) : + return [] + + def get_area_height(self): + return self.context.area.height + + def is_in_rect(self, x, y): + area_height = self.get_area_height() + + widget_y = area_height - self.y_screen + if ( + (self.x_screen <= x <= (self.x_screen + self.width)) and + (widget_y >= y >= (widget_y - self.height)) + ): + return True + + return False + + def text_input(self, event): + return False + + def mouse_down(self, x, y): + return self.is_in_rect(x,y) + + def mouse_down_right(self, x, y): + return self.is_in_rect(x,y) + + def mouse_up(self, x, y): + pass + + def set_mouse_enter(self, mouse_enter_func): + self.mouse_enter_func = mouse_enter_func + + def call_mouse_enter(self): + try: + if self.mouse_enter_func: + self.mouse_enter_func(self) + except: + pass + + def mouse_enter(self, event, x, y): + self.call_mouse_enter() + + def set_mouse_exit(self, mouse_exit_func): + self.mouse_exit_func = mouse_exit_func + + def call_mouse_exit(self): + try: + if self.mouse_exit_func: + self.mouse_exit_func(self) + except: + pass + + def mouse_exit(self, event, x, y): + self.call_mouse_exit() + + def mouse_move(self, x, y): + pass \ No newline at end of file diff --git a/blenderkit/colors.py b/blenderkit/colors.py index c61d9fa0..fe2fb1ac 100644 --- a/blenderkit/colors.py +++ b/blenderkit/colors.py @@ -18,6 +18,8 @@ # this module defines color palette for BlenderKit UI +WHITE = (1, 1, 1, .9) + TEXT = (.9, .9, .9, .6) GREEN = (.9, 1, .9, .6) RED = (1, .5, .5, .8) diff --git a/blenderkit/icons.py b/blenderkit/icons.py index 3c6cea4b..5d877b25 100644 --- a/blenderkit/icons.py +++ b/blenderkit/icons.py @@ -27,6 +27,7 @@ icon_collections = {} icons_read = { 'fp.png': 'free', 'flp.png': 'full', + 'test.jpg': 'test', } @@ -44,6 +45,11 @@ def register_icons(): for ir in icons_read.keys(): pcoll.load(icons_read[ir], os.path.join(icons_dir, ir), 'IMAGE') + # iprev = pcoll.new(icons_read[ir]) + # img = bpy.data.images.load(os.path.join(icons_dir, ir)) + # iprev.image_size = (img.size[0], img.size[1]) + # iprev.image_pixels_float = img.pixels[:] + icon_collections["main"] = pcoll diff --git a/blenderkit/search.py b/blenderkit/search.py index 880884ec..2252d3f1 100644 --- a/blenderkit/search.py +++ b/blenderkit/search.py @@ -16,8 +16,6 @@ # # ##### END GPL LICENSE BLOCK ##### - - from blenderkit import paths, utils, categories, ui, colors, bkit_oauth, version_checker, tasks_queue, rerequests, \ resolutions @@ -51,6 +49,7 @@ import json import math import logging + bk_logger = logging.getLogger('blenderkit') search_start_time = 0 @@ -320,6 +319,7 @@ def timer_update(): global first_time preferences = bpy.context.preferences.addons['blenderkit'].preferences if first_time and not bpy.app.background: # first time + first_time = False if preferences.show_on_start: # TODO here it should check if there are some results, and only open assetbar if this is the case, not search. @@ -437,11 +437,14 @@ def load_previews(): if not r['thumbnail_small']: tpath = paths.get_addon_thumbnail_path('thumbnail_not_available.jpg') + if not os.path.exists(tpath): + continue iname = utils.previmg_name(i) # if os.path.exists(tpath): # sometimes we are unlucky... img = bpy.data.images.get(iname) - if img is None and os.path.exists(tpath): + + if img is None: img = bpy.data.images.load(tpath) img.name = iname elif img.filepath != tpath: @@ -897,7 +900,7 @@ class Searcher(threading.Thread): # result ordering: _score - relevance, score - BlenderKit score order = [] if params['free_first']: - order = ['-is_free',] + order = ['-is_free', ] if query.get('query') is None and query.get('category_subtree') == None: # assumes no keywords and no category, thus an empty search that is triggered on start. # orders by last core file upload diff --git a/blenderkit/ui.py b/blenderkit/ui.py index 9f60d471..0dfed1d6 100644 --- a/blenderkit/ui.py +++ b/blenderkit/ui.py @@ -21,6 +21,7 @@ from blenderkit import paths, ratings, utils, search, upload, ui_bgl, download, bg_blender, colors, tasks_queue, \ ui_panels,icons + import bpy import math, random @@ -304,7 +305,6 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None): isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1])) estimated_height = 2 * ttipmargin + textmargin + isizey - if estimated_height > y: scaledown = y / (estimated_height) scale *= scaledown @@ -350,7 +350,6 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None): bgcol) # main preview image ui_bgl.draw_image(x, y - isizey - ttipmargin, isizex, isizey, img, 1) - # text overlay background ui_bgl.draw_rect(x - ttipmargin, y - 2 * ttipmargin - isizey, @@ -436,6 +435,23 @@ def draw_tooltip(x, y, text='', author='', img=None, gravatar=None): t = time.time() +def draw_tooltip_with_author(asset_data, x,y): + # TODO move this lazy loading into a function and don't duplicate through the code + + img = get_large_thumbnail_image(asset_data) + gimg = None + atip = '' + if bpy.context.window_manager.get('bkit authors') is not None: + a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id']) + if a is not None and a != '': + if a.get('gravatarImg') is not None: + gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash']) + atip = a['tooltip'] + + # scene = bpy.context.scene + # ui_props = scene.blenderkitUI + draw_tooltip(x,y, text=asset_data['tooltip'], author=atip, img=img, + gravatar=gimg) def draw_tooltip_old(x, y, text='', author='', img=None): region = bpy.context.region @@ -678,6 +694,22 @@ def is_upload_old(asset_data): return (age.days - old.days) return 0 +def get_large_thumbnail_image(asset_data): + '''Get thumbnail image from asset data''' + scene = bpy.context.scene + ui_props = scene.blenderkitUI + iname = utils.previmg_name(ui_props.active_index, fullsize=True) + directory = paths.get_temp_dir('%s_search' % mappingdict[ui_props.asset_type]) + tpath = os.path.join(directory, asset_data['thumbnail']) + if not asset_data['thumbnail']: + tpath = paths.get_addon_thumbnail_path('thumbnail_not_available.jpg') + + if asset_data['assetType'] == 'hdr': + colorspace = 'Non-Color' + else: + colorspace = 'sRGB' + img = utils.get_hidden_image(tpath, iname, colorspace=colorspace) + return img def draw_asset_bar(self, context): s = bpy.context.scene @@ -817,63 +849,17 @@ def draw_asset_bar(self, context): # report = 'BlenderKit - No matching results found.' # ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin, # ui_props.bar_y - 25 - ui_props.margin, 15) + if ui_props.draw_tooltip: + r = search_results[ui_props.active_index] + draw_tooltip_with_author(r, ui_props.mouse_x, ui_props.mouse_y) s = bpy.context.scene props = utils.get_search_props() # if props.report != '' and props.is_searching or props.search_error: # ui_bgl.draw_text(props.report, ui_props.bar_x, # ui_props.bar_y - 15 - ui_props.margin - ui_props.bar_height, 15) - props = s.blenderkitUI - if props.draw_tooltip: - # TODO move this lazy loading into a function and don't duplicate through the code - iname = utils.previmg_name(ui_props.active_index, fullsize=True) - - directory = paths.get_temp_dir('%s_search' % mappingdict[props.asset_type]) - sr = s.get('search results') - if sr != None and -1 < ui_props.active_index < len(sr): - r = sr[ui_props.active_index] - tpath = os.path.join(directory, r['thumbnail']) - if not r['thumbnail']: - tpath = paths.get_addon_thumbnail_path('thumbnail_not_available.jpg') - - # img = bpy.data.images.get(iname) - # if img == None or img.filepath != tpath: - # # TODO replace it with a function - # if os.path.exists(tpath): - # - # if img is None: - # img = bpy.data.images.load(tpath) - # img.name = iname - # else: - # if img.filepath != tpath: - # # todo replace imgs reloads with a method that forces unpack for thumbs. - # if img.packed_file is not None: - # img.unpack(method='USE_ORIGINAL') - # img.filepath = tpath - # img.reload() - # img.name = iname - # else: - # iname = utils.previmg_name(ui_props.active_index) - # img = bpy.data.images.get(iname) - # if img: - # img.colorspace_settings.name = 'sRGB' - if r['assetType'] == 'hdr': - colorspace = 'Non-Color' - else: - colorspace = 'sRGB' - img = utils.get_hidden_image(tpath, iname, colorspace=colorspace) - gimg = None - atip = '' - if bpy.context.window_manager.get('bkit authors') is not None: - a = bpy.context.window_manager['bkit authors'].get(r['author']['id']) - if a is not None and a != '': - if a.get('gravatarImg') is not None: - gimg = utils.get_hidden_image(a['gravatarImg'], a['gravatarHash']) - atip = a['tooltip'] - draw_tooltip(ui_props.mouse_x, ui_props.mouse_y, text=ui_props.tooltip, author=atip, img=img, - gravatar=gimg) if ui_props.dragging and ( ui_props.draw_drag_image or ui_props.draw_snapped_bounds) and ui_props.active_index > -1: @@ -1303,9 +1289,6 @@ class AssetBarOperator(bpy.types.Operator): ui_props.mouse_x = 0 ui_props.mouse_y = self.region.height - mx = event.mouse_x - my = event.mouse_y - ui_props.draw_tooltip = True # only generate tooltip once in a while @@ -1472,6 +1455,7 @@ class AssetBarOperator(bpy.types.Operator): my = event.mouse_y - r.y if event.value == 'PRESS' and mouse_in_asset_bar(mx, my): + # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT') bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu') return {'RUNNING_MODAL'} @@ -1803,6 +1787,205 @@ class UndoWithContext(bpy.types.Operator): return {'FINISHED'} +def draw_callback_dragging(self, context): + img = bpy.data.images.get(self.iname) + linelength = 35 + scene = bpy.context.scene + ui_props = scene.blenderkitUI + ui_bgl.draw_image(self.mouse_x + linelength, self.mouse_y - linelength - ui_props.thumb_size, + ui_props.thumb_size, ui_props.thumb_size, img, 1) + ui_bgl.draw_line2d(self.mouse_x, self.mouse_y, self.mouse_x + linelength, + self.mouse_y - linelength, 2, colors.WHITE) + + +def draw_callback_3d_dragging(self, context): + ''' Draw snapped bbox while dragging. ''' + if not utils.guard_from_crash(): + return + ui_props = context.scene.blenderkitUI + # print(ui_props.asset_type, self.has_hit, self.snapped_location) + if ui_props.asset_type == 'MODEL': + if self.has_hit: + draw_bbox(self.snapped_location, self.snapped_rotation, self.snapped_bbox_min, self.snapped_bbox_max) + + +class AssetDragOperator(bpy.types.Operator): + """Draw a line with the mouse""" + bl_idname = "view3d.asset_drag_drop" + bl_label = "BlenderKit asset drag drop" + + asset_search_index: IntProperty(name="Active Index", default=0) + + def handlers_remove(self): + bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW') + bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW') + + def mouse_release(self): + scene = bpy.context.scene + ui_props = scene.blenderkitUI + + if not self.has_hit: + return {'RUNNING_MODAL'} + + if ui_props.asset_type == 'MODEL': + target_object = '' + if self.object_name is not None: + target_object = self.object_name + target_slot = '' + + if ui_props.asset_type == 'MATERIAL': + # first, test if object can have material applied. + object = bpy.data.objects[self.object_name] + if object is not None and not object.is_library_indirect and object.type == 'MESH': + target_object = object.name + # create final mesh to extract correct material slot + depsgraph = bpy.context.evaluated_depsgraph_get() + object_eval = object.evaluated_get(depsgraph) + temp_mesh = object_eval.to_mesh() + target_slot = temp_mesh.polygons[self.face_index].material_index + object_eval.to_mesh_clear() + else: + self.report({'WARNING'}, "Invalid or library object as input:") + target_object = '' + target_slot = '' + + if abs(self.start_mouse_x - self.mouse_x) < 20 and abs(self.start_mouse_y - self.mouse_y)<20: + #no dragging actually this was a click. + self.snapped_location = scene.cursor.location + self.snapped_rotation = (0,0,0) + if ui_props.asset_type in ('MATERIAL',): + ao = bpy.context.active_object + if ao != None and not ao.is_library_indirect: + target_object = bpy.context.active_object.name + target_slot = bpy.context.active_object.active_material_index + # change snapped location for placing material downloader. + self.snapped_location = bpy.context.active_object.location + else: + target_object = '' + target_slot = '' + + + # picking of assets and using them + if ui_props.asset_type == 'MATERIAL': + if target_object != '': + # position is for downloader: + loc = self.snapped_location + rotation = (0, 0, 0) + + utils.automap(target_object, target_slot=target_slot, + tex_size=self.asset_data.get('texture_size_meters', 1.0)) + bpy.ops.scene.blenderkit_download(True, + # asset_type=ui_props.asset_type, + asset_index=self.asset_search_index, + model_location=loc, + model_rotation=rotation, + target_object=target_object, + material_target_slot=target_slot) + + + elif ui_props.asset_type == 'MODEL': + + if 'particle_plants' in self.asset_data['tags']: + bpy.ops.object.blenderkit_particles_drop("INVOKE_DEFAULT", + asset_search_index=self.asset_search_index, + model_location=self.snapped_location, + model_rotation=self.snapped_rotation, + target_object=target_object) + else: + bpy.ops.scene.blenderkit_download(True, + # asset_type=ui_props.asset_type, + asset_index=self.asset_search_index, + model_location=self.snapped_location, + model_rotation=self.snapped_rotation, + target_object=target_object) + + else: + bpy.ops.scene.blenderkit_download( # asset_type=ui_props.asset_type, + asset_index=self.asset_search_index) + + def modal(self, context, event): + scene = bpy.context.scene + ui_props = scene.blenderkitUI + context.area.tag_redraw() + + # if event.type == 'MOUSEMOVE': + if not hasattr(self,'start_mouse_x'): + self.start_mouse_x = event.mouse_region_x + self.start_mouse_y = event.mouse_region_y + + self.mouse_x = event.mouse_region_x + self.mouse_y = event.mouse_region_y + + if event.type == 'LEFTMOUSE' and event.value == 'RELEASE': + self.mouse_release() + self.handlers_remove() + return {'FINISHED'} + + elif event.type in {'RIGHTMOUSE', 'ESC'}: + self.handlers_remove() + return {'CANCELLED'} + + sprops = bpy.context.scene.blenderkit_models + if event.type == 'WHEELUPMOUSE': + sprops.offset_rotation_amount += sprops.offset_rotation_step + elif event.type == 'WHEELDOWNMOUSE': + sprops.offset_rotation_amount -= sprops.offset_rotation_step + + #### TODO - this snapping code below is 3x in this file.... refactor it. + self.has_hit, self.snapped_location, self.snapped_normal, self.snapped_rotation, self.face_index, object, self.matrix = mouse_raycast( + context, event.mouse_region_x, event.mouse_region_y) + if object is not None: + self.object_name =object.name + + + + # MODELS can be dragged on scene floor + if not self.has_hit and ui_props.asset_type == 'MODEL': + self.has_hit, self.snapped_location, self.snapped_normal, self.snapped_rotation, self.face_index, object, self.matrix = floor_raycast( + context, + event.mouse_region_x, event.mouse_region_y) + if object is not None: + self.object_name = object.name + + if ui_props.asset_type == 'MODEL': + self.snapped_bbox_min = Vector(self.asset_data['bbox_min']) + self.snapped_bbox_max = Vector(self.asset_data['bbox_max']) + + return {'RUNNING_MODAL'} + + def invoke(self, context, event): + if context.area.type == 'VIEW_3D': + # the arguments we pass the the callback + args = (self, context) + # Add the region OpenGL drawing callback + # draw in view space with 'POST_VIEW' and 'PRE_VIEW' + self.iname = utils.previmg_name(self.asset_search_index) + + self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_dragging, args, 'WINDOW', 'POST_PIXEL') + self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_dragging, args, 'WINDOW', + 'POST_VIEW') + + self.mouse_x = 0 + self.mouse_y = 0 + + self.has_hit = False + self.snapped_location = (0,0,0) + self.snapped_normal = (0,0,1) + self.snapped_rotation = (0,0,0) + self.face_index = 0 + object = None + self.matrix = None + + sr = bpy.context.scene['search results'] + self.asset_data = sr[self.asset_search_index] + + context.window_manager.modal_handler_add(self) + return {'RUNNING_MODAL'} + else: + self.report({'WARNING'}, "View3D not found, cannot run operator") + return {'CANCELLED'} + + class RunAssetBarWithContext(bpy.types.Operator): """Regenerate cobweb""" bl_idname = "object.run_assetbar_fix_context" @@ -1816,13 +1999,19 @@ class RunAssetBarWithContext(bpy.types.Operator): def execute(self, context): C_dict = utils.get_fake_context(context) if C_dict.get('window'): # no 3d view, no asset bar. - bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False) + preferences = bpy.context.preferences.addons['blenderkit'].preferences + if preferences.experimental_features: + bpy.ops.view3d.blenderkit_asset_bar_widget(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False) + + else: + bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False) return {'FINISHED'} classes = ( AssetBarOperator, # AssetBarExperiment, + AssetDragOperator, RunAssetBarWithContext, TransferBlenderkitData, UndoWithContext, diff --git a/blenderkit/ui_panels.py b/blenderkit/ui_panels.py index b194568c..4948b651 100644 --- a/blenderkit/ui_panels.py +++ b/blenderkit/ui_panels.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### -from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions +from blenderkit import paths, ratings, utils, download, categories, icons, search, resolutions, ui from bpy.types import ( Panel @@ -313,11 +313,19 @@ def draw_assetbar_show_hide(layout, props): else: icon = 'HIDE_ON' ttip = 'Click to Show Asset Bar' - op = layout.operator('view3d.blenderkit_asset_bar', text='', icon=icon) - op.keep_running = False - op.do_search = False - op.tooltip = ttip + preferences = bpy.context.preferences.addons['blenderkit'].preferences + if preferences.experimental_features: + op = layout.operator('view3d.blenderkit_asset_bar_widget', text = '', icon = icon) + op.keep_running = False + op.do_search = False + op.tooltip = ttip + else: + op = layout.operator('view3d.blenderkit_asset_bar', text='', icon=icon) + op.keep_running = False + op.do_search = False + + op.tooltip = ttip def draw_panel_model_search(self, context): @@ -403,7 +411,7 @@ class VIEW3D_PT_blenderkit_model_properties(Panel): draw_panel_model_rating(self, context) layout.label(text='Asset tools:') - draw_asset_context_menu(self, context, ad, from_panel=True) + draw_asset_context_menu(self.layout, context, ad, from_panel=True) # if 'rig' in ad['tags']: # # layout.label(text = 'can make proxy') # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy') @@ -447,7 +455,7 @@ class NODE_PT_blenderkit_material_properties(Panel): draw_panel_material_ratings(self, context) layout.label(text='Asset tools:') - draw_asset_context_menu(self, context, ad, from_panel=True) + draw_asset_context_menu(self.layout, context, ad, from_panel=True) # if 'rig' in ad['tags']: # # layout.label(text = 'can make proxy') # layout.operator('object.blenderkit_make_proxy', text = 'Make Armature proxy') @@ -934,7 +942,6 @@ class VIEW3D_PT_blenderkit_unified(Panel): user_preferences = bpy.context.preferences.addons['blenderkit'].preferences wm = bpy.context.window_manager layout = self.layout - # layout.prop_tabs_enum(ui_props, "asset_type", icon_only = True) row = layout.row() @@ -1122,8 +1129,7 @@ class BlenderKitWelcomeOperator(bpy.types.Operator): return wm.invoke_props_dialog(self) -def draw_asset_context_menu(self, context, asset_data, from_panel=False): - layout = self.layout +def draw_asset_context_menu(layout, context, asset_data, from_panel=False): ui_props = context.scene.blenderkitUI author_id = str(asset_data['author'].get('id')) @@ -1269,7 +1275,7 @@ def draw_asset_context_menu(self, context, asset_data, from_panel=False): op.asset_id = asset_data['id'] op.state = 'rejected' - if author_id == str(profile['user']['id']): + if author_id == str(profile['user']['id']) or utils.profile_is_validator(): layout.label(text='Management tools:') row = layout.row() @@ -1277,6 +1283,7 @@ def draw_asset_context_menu(self, context, asset_data, from_panel=False): op = layout.operator('wm.blenderkit_fast_metadata', text='Fast Edit Metadata') op.asset_id = asset_data['id'] + if author_id == str(profile['user']['id']): row = layout.row() row.operator_context = 'INVOKE_DEFAULT' op = row.operator('object.blenderkit_change_status', text='Delete') @@ -1342,12 +1349,101 @@ class OBJECT_MT_blenderkit_asset_menu(bpy.types.Menu): def draw(self, context): ui_props = context.scene.blenderkitUI + sr = bpy.context.scene['search results'] + asset_data = sr[ui_props.active_index] + draw_asset_context_menu(self.layout, context, asset_data, from_panel=False) + + # ui_props = context.scene.blenderkitUI + # # sr = bpy.context.scene['search results'] + # asset_data = sr[ui_props.active_index] + # layout = self.layout + # row = layout.row() + # split = row.split(factor=0.2) + # col = split.column() + # op = col.operator('view3d.asset_drag_drop') + # op.asset_search_index=ui_props.active_index + # + # draw_asset_context_menu(col, context, asset_data, from_panel=False) + # split = split.split(factor=0.3) + # col1 = split.column() + # box = col1.box() + # utils.label_multiline(box, asset_data['tooltip']) + # col2 = split.column() + # + # pcoll = icons.icon_collections["main"] + # my_icon = pcoll['test'] + # row = col2.row() + # row.scale_y = 4 + # row.template_icon(icon_value=my_icon.icon_id, scale=2.0) + # # col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0) + # box2 = col2.box() + # + # box2.label(text='and heere goes the rating') + # box2.label(text='************') + # box2.label(text='dadydadadada') + +class AssetPopupCard(bpy.types.Operator): + """Generate Cycles thumbnail for model assets""" + bl_idname = "wm.blenderkit_asset_popup" + bl_label = "BlenderKit asset popup" + # bl_options = {'REGISTER', 'INTERNAL'} + bl_options = {'REGISTER',} + + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + ui_props = context.scene.blenderkitUI + sr = bpy.context.scene['search results'] asset_data = sr[ui_props.active_index] + layout = self.layout + row = layout.row() + split = row.split(factor=0.2) + col = split.column() + op = col.operator('view3d.asset_drag_drop') + op.asset_search_index = ui_props.active_index + draw_asset_context_menu(col, context, asset_data, from_panel=False) + split = split.split(factor=0.5) + col1 = split.column() + box = col1.box() + utils.label_multiline(box,asset_data['tooltip'], width = 300) + + col2 = split.column() + + + pcoll = icons.icon_collections["main"] + my_icon = pcoll['test'] + col2.template_icon(icon_value=my_icon.icon_id, scale=20.0) + # col2.template_icon(icon_value=self.img.preview.icon_id, scale=10.0) + box2 = col2.box() + + # draw_ratings(box2, context, asset_data) + box2.label(text = 'Ratings') + # print(tp, dir(tp)) + # if not hasattr(self, 'first_draw'):# try to redraw because of template preview which needs update + # for region in context.area.regions: + # region.tag_redraw() + # self.first_draw = True + + def execute(self, context): + print('execute') + return {'FINISHED'} - draw_asset_context_menu(self, context, asset_data, from_panel=False) + def invoke(self, context, event): + wm = context.window_manager + ui_props = context.scene.blenderkitUI + ui_props.draw_tooltip = False + sr = bpy.context.scene['search results'] + asset_data = sr[ui_props.active_index] + self.img = ui.get_large_thumbnail_image(asset_data) + # self.tex = utils.get_hidden_texture(self.img) + # self.tex.update_tag() + bl_label = asset_data['name'] + return wm.invoke_props_dialog(self, width = 700) class OBJECT_MT_blenderkit_login_menu(bpy.types.Menu): bl_label = "BlenderKit login/signup:" @@ -1435,8 +1531,8 @@ class UrlPopupDialog(bpy.types.Operator): class LoginPopupDialog(bpy.types.Operator): - """Generate Cycles thumbnail for model assets""" - bl_idname = "wm.blenderkit_url_dialog" + """Popup a dialog which enables the user to log in after being logged out automatically.""" + bl_idname = "wm.blenderkit_login_dialog" bl_label = "BlenderKit login" bl_options = {'REGISTER', 'INTERNAL'} @@ -1502,10 +1598,15 @@ def draw_panel_categories(self, context): row = row.split(factor=.8, align=True) # row = split.split() ctext = '%s (%i)' % (c['name'], c['assetCount']) - op = row.operator('view3d.blenderkit_asset_bar', text=ctext) - op.do_search = True - op.keep_running = True - op.category = c['slug'] + + preferences = bpy.context.preferences.addons['blenderkit'].preferences + if preferences.experimental_features: + op = row.operator('view3d.blenderkit_asset_bar_widget', text=ctext) + else: + op = row.operator('view3d.blenderkit_asset_bar', text=ctext) + op.do_search = True + op.keep_running = True + op.category = c['slug'] # TODO enable subcategories, now not working due to some bug on server probably if len(c['children']) > 0 and c['assetCount'] > 15: # row = row.split() @@ -1602,6 +1703,7 @@ classes = ( # OBJECT_MT_blenderkit_resolution_menu, OBJECT_MT_blenderkit_asset_menu, OBJECT_MT_blenderkit_login_menu, + AssetPopupCard, UrlPopupDialog, BlenderKitWelcomeOperator, ) diff --git a/blenderkit/utils.py b/blenderkit/utils.py index 38e34551..fde5b002 100644 --- a/blenderkit/utils.py +++ b/blenderkit/utils.py @@ -39,6 +39,10 @@ NORMAL_PRIORITY_CLASS = 0x00000020 REALTIME_PRIORITY_CLASS = 0x00000100 +def experimental_enabled(): + preferences = bpy.context.preferences.addons['blenderkit'].preferences + return preferences.experimental_features + def get_process_flags(): flags = BELOW_NORMAL_PRIORITY_CLASS if sys.platform != 'win32': # TODO test this on windows @@ -299,14 +303,14 @@ def uploadable_asset_poll(): return ui_props.hdr_upload_image is not None return True -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) +def get_hidden_texture(img, force_reload=False): + # i = get_hidden_image(tpath, bdata_name, force_reload=force_reload) + # bdata_name = f".{bdata_name}" + t = bpy.data.textures.get(img.name) if t is None: - t = bpy.data.textures.new('.test', 'IMAGE') - if t.image != i: - t.image = i + t = bpy.data.textures.new(img.name, 'IMAGE') + if t.image != img: + t.image = img return t -- cgit v1.2.3