Skip to content
Snippets Groups Projects
asset_bar_op.py 23.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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:
    
            wm = bpy.context.window_manager
            sr = wm.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):
    
    Vilém Duha's avatar
    Vilém Duha committed
        ui_props = bpy.context.scene.blenderkitUI
        if ui_props.turn_off:
            ui_props.turn_off = False
            self.finish()
    
    
        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'}
    
    
    Vilém Duha's avatar
    Vilém Duha committed
        if self.check_ui_resized(context):
            self.update_ui_size(context)
            self.update_layout(context)
    
        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'})
    
    
    Vilém Duha's avatar
    Vilém Duha committed
        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
    
    
    Vilém Duha's avatar
    Vilém Duha committed
        def check_ui_resized(self,context):
            region = context.region
            area = context.area
            ui_props = bpy.context.scene.blenderkitUI
            ui_scale = bpy.context.preferences.view.ui_scale
    
            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
    
            bar_width = region.width - self.bar_x - self.bar_end
            if bar_width != self.bar_width:
                return True
            return False
    
    
        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
    
    
    Vilém Duha's avatar
    Vilém Duha committed
            self.bar_width = region.width - self.bar_x - self.bar_end
    
    
            self.wcount = math.floor(
                (self.bar_width) / (self.button_size))
    
    
            search_results = bpy.context.window_manager.get('search results')
    
    Vilém Duha's avatar
    Vilém Duha committed
            # we need to init all possible thumb previews in advance/
            self.hcount = user_preferences.max_assetbar_rows
            # 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
    
    
    Vilém Duha's avatar
    Vilém Duha committed
        def update_layout(self, context):
            pass;
    
    
        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)
    
            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):
    
            # context.window.cursor_warp(event.mouse_x, event.mouse_y - 20);
    
    
    
            self.show_tooltip()
    
            if self.active_index != widget.search_index:
                scene = bpy.context.scene
    
                wm = bpy.context.window_manager
                sr = wm['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']
    
    Vilém Duha's avatar
    Vilém Duha committed
    
                properties_width = 0
                for r in bpy.context.area.regions:
                    if r.type == 'UI':
                        properties_width = r.width
                tooltip_x = min(widget.x_screen + widget.width, bpy.context.region.width - self.tooltip_panel.width -properties_width)
    
                self.tooltip_panel.update(tooltip_x, widget.y_screen + widget.height)
    
                self.tooltip_panel.layout_widgets()
    
                # bpy.ops.wm.blenderkit_asset_popup('INVOKE_DEFAULT')
    
    
    
        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.window_manager.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):
    
    Vilém Duha's avatar
    Vilém Duha committed
            sr = bpy.context.window_manager.get('search results')
            if not sr:
                return
    
            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.window_manager['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.window_manager['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):
            self.scroll_offset += self.wcount * self.hcount
            self.scroll_update()
    
        def scroll_down(self, widget):
            self.scroll_offset -= self.wcount * self.hcount
            self.scroll_update()
    
    
    Vilém Duha's avatar
    Vilém Duha committed
        def update_sizes(self):
            properties_width = 0
            for r in bpy.context.area.regions:
                if r.type == 'UI':
                    properties_width = r.width
            tooltip_x = min(widget.x_screen + widget.width,
                            bpy.context.region.width - self.tooltip_panel.width - properties_width)
    
            # print(widget.x_screen + widget.width, bpy.context.region.width - self.tooltip_panel.width)
    
    Vilém Duha's avatar
    Vilém Duha committed
    
    
    
    def register():
        bpy.utils.register_class(BlenderKitAssetBarOperator)
    
    
    def unregister():
        bpy.utils.unregister_class(BlenderKitAssetBarOperator)