Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVilém Duha <vilda.novak@gmail.com>2021-01-17 15:35:23 +0300
committerVilém Duha <vilda.novak@gmail.com>2021-01-17 15:35:23 +0300
commite237c47811583d92c2ad07f00591cf4499fa0ae4 (patch)
treee644088d8bcb03cad9e8c6ad4f4579823ff2b4ca
parentebdf1861dc568a9b4b92b29a9b8f7117ad1eec05 (diff)
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.
-rw-r--r--blenderkit/__init__.py23
-rw-r--r--blenderkit/asset_bar_op.py591
-rw-r--r--blenderkit/bl_ui_widgets/__init__.py36
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_button.py192
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_drag_panel.py58
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_draw_op.py83
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_label.py57
-rw-r--r--blenderkit/bl_ui_widgets/bl_ui_widget.py189
-rw-r--r--blenderkit/colors.py2
-rw-r--r--blenderkit/icons.py6
-rw-r--r--blenderkit/search.py11
-rw-r--r--blenderkit/ui.py299
-rw-r--r--blenderkit/ui_panels.py138
-rw-r--r--blenderkit/utils.py18
14 files changed, 1619 insertions, 84 deletions
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