mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-20 13:22:58 +00:00

this is a complex fix that required to change some basic behaviours. - the temp folder from blenderkit_data was moved to system temp folder - There's a cleanup function for the old folder. - search itself doesn't create files on drive(this rework will continue to try not to write also some other data, like gravatars, but cache them in mem only) further fixes: - assetbar woudln't draw if there wasn't a thumbnail in an asset - categories panel poll function was fixed for brushes - fetching tokens from rerequests wasn't writing them into prefs TODO: create a popup that asks if categories can be downloaded and an example search can be performed.
1904 lines
77 KiB
Python
1904 lines
77 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
|
|
if "bpy" in locals():
|
|
import importlib
|
|
|
|
paths = importlib.reload(paths)
|
|
ratings = importlib.reload(ratings)
|
|
utils = importlib.reload(utils)
|
|
search = importlib.reload(search)
|
|
upload = importlib.reload(upload)
|
|
ui_bgl = importlib.reload(ui_bgl)
|
|
download = importlib.reload(download)
|
|
bg_blender = importlib.reload(bg_blender)
|
|
colors = importlib.reload(colors)
|
|
tasks_queue = importlib.reload(tasks_queue)
|
|
else:
|
|
from blenderkit import paths, ratings, utils, search, upload, ui_bgl, download, bg_blender, colors, tasks_queue
|
|
|
|
import bpy
|
|
|
|
import math, random
|
|
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
StringProperty
|
|
)
|
|
|
|
from bpy_extras import view3d_utils
|
|
import mathutils
|
|
from mathutils import Vector
|
|
import time
|
|
import os
|
|
|
|
handler_2d = None
|
|
handler_3d = None
|
|
active_area = None
|
|
active_area = None
|
|
active_window = None
|
|
active_region = None
|
|
|
|
reports = []
|
|
|
|
mappingdict = {
|
|
'MODEL': 'model',
|
|
'SCENE': 'scene',
|
|
'MATERIAL': 'material',
|
|
'TEXTURE': 'texture',
|
|
'BRUSH': 'brush'
|
|
}
|
|
|
|
verification_icons = {
|
|
'ready': 'vs_ready.png',
|
|
'deleted': 'vs_deleted.png',
|
|
'uploaded': 'vs_uploaded.png',
|
|
'uploading': 'vs_uploading.png',
|
|
'on_hold': 'vs_on_hold.png',
|
|
'validated': None,
|
|
'rejected': 'vs_rejected.png'
|
|
|
|
}
|
|
|
|
|
|
# class UI_region():
|
|
# def _init__(self, parent = None, x = 10,y = 10 , width = 10, height = 10, img = None, col = None):
|
|
|
|
def get_approximate_text_width(st):
|
|
size = 10
|
|
for s in st:
|
|
if s in 'i|':
|
|
size += 2
|
|
elif s in ' ':
|
|
size += 4
|
|
elif s in 'sfrt':
|
|
size += 5
|
|
elif s in 'ceghkou':
|
|
size += 6
|
|
elif s in 'PadnBCST3E':
|
|
size += 7
|
|
elif s in 'GMODVXYZ':
|
|
size += 8
|
|
elif s in 'w':
|
|
size += 9
|
|
elif s in 'm':
|
|
size += 10
|
|
else:
|
|
size += 7
|
|
return size # Convert to picas
|
|
|
|
|
|
def add_report(text='', timeout=5, color=colors.GREEN):
|
|
global reports
|
|
# check for same reports and just make them longer by the timeout.
|
|
for old_report in reports:
|
|
if old_report.text == text:
|
|
old_report.timeout = old_report.age + timeout
|
|
return
|
|
report = Report(text=text, timeout=timeout, color=color)
|
|
reports.append(report)
|
|
|
|
|
|
class Report():
|
|
def __init__(self, text='', timeout=5, color=(.5, 1, .5, 1)):
|
|
self.text = text
|
|
self.timeout = timeout
|
|
self.start_time = time.time()
|
|
self.color = color
|
|
self.draw_color = color
|
|
self.age = 0
|
|
|
|
def fade(self):
|
|
fade_time = 1
|
|
self.age = time.time() - self.start_time
|
|
if self.age + fade_time > self.timeout:
|
|
alpha_multiplier = (self.timeout - self.age) / fade_time
|
|
self.draw_color = (self.color[0], self.color[1], self.color[2], self.color[3] * alpha_multiplier)
|
|
if self.age > self.timeout:
|
|
global reports
|
|
try:
|
|
reports.remove(self)
|
|
except Exception as e:
|
|
pass;
|
|
|
|
def draw(self, x, y):
|
|
if bpy.context.area == active_area:
|
|
ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
|
|
|
|
|
|
def get_asset_under_mouse(mousex, mousey):
|
|
s = bpy.context.scene
|
|
ui_props = bpy.context.scene.blenderkitUI
|
|
r = bpy.context.region
|
|
|
|
search_results = s.get('search results')
|
|
if search_results is not None:
|
|
|
|
h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
|
|
for b in range(0, h_draw):
|
|
w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)
|
|
for a in range(0, w_draw):
|
|
x = ui_props.bar_x + a * (ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset
|
|
y = ui_props.bar_y - ui_props.margin - (ui_props.thumb_size + ui_props.margin) * (b + 1)
|
|
w = ui_props.thumb_size
|
|
h = ui_props.thumb_size
|
|
|
|
if x < mousex < x + w and y < mousey < y + h:
|
|
return a + ui_props.wcount * b + ui_props.scrolloffset
|
|
|
|
# return search_results[a]
|
|
|
|
return -3
|
|
|
|
|
|
def draw_bbox(location, rotation, bbox_min, bbox_max, progress=None, color=(0, 1, 0, 1)):
|
|
ui_props = bpy.context.scene.blenderkitUI
|
|
|
|
rotation = mathutils.Euler(rotation)
|
|
|
|
smin = Vector(bbox_min)
|
|
smax = Vector(bbox_max)
|
|
v0 = Vector(smin)
|
|
v1 = Vector((smax.x, smin.y, smin.z))
|
|
v2 = Vector((smax.x, smax.y, smin.z))
|
|
v3 = Vector((smin.x, smax.y, smin.z))
|
|
v4 = Vector((smin.x, smin.y, smax.z))
|
|
v5 = Vector((smax.x, smin.y, smax.z))
|
|
v6 = Vector((smax.x, smax.y, smax.z))
|
|
v7 = Vector((smin.x, smax.y, smax.z))
|
|
|
|
arrowx = smin.x + (smax.x - smin.x) / 2
|
|
arrowy = smin.y - (smax.x - smin.x) / 2
|
|
v8 = Vector((arrowx, arrowy, smin.z))
|
|
|
|
vertices = [v0, v1, v2, v3, v4, v5, v6, v7, v8]
|
|
for v in vertices:
|
|
v.rotate(rotation)
|
|
v += Vector(location)
|
|
|
|
lines = [[0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5],
|
|
[2, 6], [3, 7], [0, 8], [1, 8]]
|
|
ui_bgl.draw_lines(vertices, lines, color)
|
|
if progress != None:
|
|
color = (color[0], color[1], color[2], .2)
|
|
progress = progress * .01
|
|
vz0 = (v4 - v0) * progress + v0
|
|
vz1 = (v5 - v1) * progress + v1
|
|
vz2 = (v6 - v2) * progress + v2
|
|
vz3 = (v7 - v3) * progress + v3
|
|
rects = (
|
|
(v0, v1, vz1, vz0),
|
|
(v1, v2, vz2, vz1),
|
|
(v2, v3, vz3, vz2),
|
|
(v3, v0, vz0, vz3))
|
|
for r in rects:
|
|
ui_bgl.draw_rect_3d(r, color)
|
|
|
|
|
|
def get_rating_scalevalues(asset_type):
|
|
xs = []
|
|
if asset_type == 'model':
|
|
scalevalues = (0.5, 1, 2, 5, 10, 25, 50, 100, 250)
|
|
for v in scalevalues:
|
|
a = math.log2(v)
|
|
x = (a + 1) * (1. / 9.)
|
|
xs.append(x)
|
|
else:
|
|
scalevalues = (0.2, 1, 2, 3, 4, 5)
|
|
for v in scalevalues:
|
|
a = v
|
|
x = v / 5.
|
|
xs.append(x)
|
|
return scalevalues, xs
|
|
|
|
|
|
def draw_ratings_bgl():
|
|
# return;
|
|
ui = bpy.context.scene.blenderkitUI
|
|
|
|
rating_possible, rated, asset, asset_data = is_rating_possible()
|
|
|
|
if rating_possible: # (not rated or ui_props.rating_menu_on):
|
|
bkit_ratings = asset.bkit_ratings
|
|
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
|
|
textcol = (1, 1, 1, 1)
|
|
|
|
r = bpy.context.region
|
|
font_size = int(ui.rating_ui_scale * 20)
|
|
|
|
if ui.rating_button_on:
|
|
img = utils.get_thumbnail('star_white.png')
|
|
|
|
ui_bgl.draw_image(ui.rating_x,
|
|
ui.rating_y - ui.rating_button_width,
|
|
ui.rating_button_width,
|
|
ui.rating_button_width,
|
|
img, 1)
|
|
|
|
# if ui_props.asset_type != 'BRUSH':
|
|
# thumbnail_image = props.thumbnail
|
|
# else:
|
|
# b = utils.get_active_brush()
|
|
# thumbnail_image = b.icon_filepath
|
|
|
|
directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
|
|
tpath = os.path.join(directory, asset_data['thumbnail_small'])
|
|
|
|
img = utils.get_hidden_image(tpath, 'rating_preview')
|
|
ui_bgl.draw_image(ui.rating_x + ui.rating_button_width,
|
|
ui.rating_y - ui.rating_button_width,
|
|
ui.rating_button_width,
|
|
ui.rating_button_width,
|
|
img, 1)
|
|
# ui_bgl.draw_text( 'rate asset %s' % asset_data['name'],r.width - rating_button_width + margin, margin, font_size)
|
|
return
|
|
|
|
ui_bgl.draw_rect(ui.rating_x,
|
|
ui.rating_y - ui.rating_ui_height - 2 * ui.margin - font_size,
|
|
ui.rating_ui_width + ui.margin,
|
|
ui.rating_ui_height + 2 * ui.margin + font_size,
|
|
bgcol)
|
|
if asset_data['assetType'] == 'model':
|
|
ui_img_name = 'rating_ui.png'
|
|
else:
|
|
ui_img_name = 'rating_ui_empty.png'
|
|
text = 'Try to estimate how many hours it would take for a professional artist to create this asset:'
|
|
tx = ui.rating_x + ui.workhours_bar_x
|
|
# draw_text_block(x=tx, y=ui.rating_y, width=80, font_size=20, line_height=15, text=text, color=colors.TEXT)
|
|
|
|
img = utils.get_thumbnail(ui_img_name)
|
|
ui_bgl.draw_image(ui.rating_x,
|
|
ui.rating_y - ui.rating_ui_height - 2 * ui.margin,
|
|
ui.rating_ui_width,
|
|
ui.rating_ui_height,
|
|
img, 1)
|
|
img = utils.get_thumbnail('star_white.png')
|
|
|
|
quality = bkit_ratings.rating_quality
|
|
work_hours = bkit_ratings.rating_work_hours
|
|
|
|
for a in range(0, quality):
|
|
ui_bgl.draw_image(ui.rating_x + ui.quality_stars_x + a * ui.star_size,
|
|
ui.rating_y - ui.rating_ui_height + ui.quality_stars_y,
|
|
ui.star_size,
|
|
ui.star_size,
|
|
img, 1)
|
|
|
|
img = utils.get_thumbnail('bar_slider.png')
|
|
# for a in range(0,11):
|
|
if work_hours > 0.2:
|
|
if asset_data['assetType'] == 'model':
|
|
complexity = math.log2(work_hours) + 2 # real complexity
|
|
complexity = (1. / 9.) * (complexity - 1) * ui.workhours_bar_x_max
|
|
else:
|
|
complexity = work_hours / 5 * ui.workhours_bar_x_max
|
|
ui_bgl.draw_image(
|
|
ui.rating_x + ui.workhours_bar_x + int(
|
|
complexity),
|
|
ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y,
|
|
ui.workhours_bar_slider_size,
|
|
ui.workhours_bar_slider_size, img, 1)
|
|
ui_bgl.draw_text(
|
|
str(round(work_hours, 1)),
|
|
ui.rating_x + ui.workhours_bar_x - 50,
|
|
ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y + 10, font_size)
|
|
# (0.5,1,2,4,8,16,32,64,128,256)
|
|
# ratings have to be different for models and brushes+materials.
|
|
|
|
scalevalues, xs = get_rating_scalevalues(asset_data['assetType'])
|
|
for v, x in zip(scalevalues, xs):
|
|
ui_bgl.draw_rect(ui.rating_x + ui.workhours_bar_x + int(
|
|
x * ui.workhours_bar_x_max) - 1 + ui.workhours_bar_slider_size / 2,
|
|
ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y,
|
|
2,
|
|
5,
|
|
textcol)
|
|
ui_bgl.draw_text(str(v),
|
|
ui.rating_x + ui.workhours_bar_x + int(
|
|
x * ui.workhours_bar_x_max),
|
|
ui.rating_y - ui.rating_ui_height + ui.workhours_bar_y - 30,
|
|
font_size)
|
|
if work_hours > 0.2 and quality > 0.2:
|
|
text = 'Thanks for rating asset %s' % asset_data['name']
|
|
else:
|
|
text = 'Rate asset %s.' % asset_data['name']
|
|
ui_bgl.draw_text(text,
|
|
ui.rating_x,
|
|
ui.rating_y - ui.margin - font_size,
|
|
font_size)
|
|
|
|
|
|
def draw_text_block(x=0, y=0, width=40, font_size=10, line_height=15, text='', color=colors.TEXT):
|
|
lines = text.split('\n')
|
|
nlines = []
|
|
for l in lines:
|
|
nlines.extend(search.split_subs(l, ))
|
|
|
|
column_lines = 0
|
|
for l in nlines:
|
|
ytext = y - column_lines * line_height
|
|
column_lines += 1
|
|
ui_bgl.draw_text(l, x, ytext, font_size, color)
|
|
|
|
|
|
def draw_tooltip(x, y, text='', author='', img=None, gravatar=None):
|
|
region = bpy.context.region
|
|
scale = bpy.context.preferences.view.ui_scale
|
|
t = time.time()
|
|
|
|
ttipmargin = 5
|
|
textmargin = 10
|
|
|
|
font_height = int(12 * scale)
|
|
line_height = int(15 * scale)
|
|
nameline_height = int(23 * scale)
|
|
|
|
lines = text.split('\n')
|
|
alines = author.split('\n')
|
|
ncolumns = 2
|
|
# nlines = math.ceil((len(lines) - 1) / ncolumns)
|
|
nlines = max(len(lines) - 1, len(alines)) # math.ceil((len(lines) - 1) / ncolumns)
|
|
|
|
texth = line_height * nlines + nameline_height
|
|
|
|
if max(img.size[0], img.size[1]) == 0:
|
|
return;
|
|
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
|
|
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
|
|
# we need to scale these down to have correct size if the tooltip wouldn't fit.
|
|
font_height = int(12 * scale)
|
|
line_height = int(15 * scale)
|
|
nameline_height = int(23 * scale)
|
|
|
|
lines = text.split('\n')
|
|
|
|
texth = line_height * nlines + nameline_height
|
|
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
|
|
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
|
|
|
|
name_height = int(18 * scale)
|
|
|
|
x += 2 * ttipmargin
|
|
y -= 2 * ttipmargin
|
|
|
|
width = isizex + 2 * ttipmargin
|
|
|
|
properties_width = 0
|
|
for r in bpy.context.area.regions:
|
|
if r.type == 'UI':
|
|
properties_width = r.width
|
|
|
|
x = min(x + width, region.width - properties_width) - width
|
|
|
|
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
|
|
bgcol1 = (bgcol[0], bgcol[1], bgcol[2], .6)
|
|
textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
|
|
textcol = (textcol[0], textcol[1], textcol[2], 1)
|
|
textcol_mild = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
|
|
textcol_strong = (textcol[0] * 1.3, textcol[1] * 1.3, textcol[2] * 1.3, 1)
|
|
white = (1, 1, 1, .1)
|
|
|
|
# background
|
|
ui_bgl.draw_rect(x - ttipmargin,
|
|
y - 2 * ttipmargin - isizey,
|
|
isizex + ttipmargin * 2,
|
|
2 * ttipmargin + isizey,
|
|
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,
|
|
isizex + ttipmargin * 2,
|
|
2 * ttipmargin + texth,
|
|
bgcol1)
|
|
# draw gravatar
|
|
gsize = 40
|
|
if gravatar is not None:
|
|
# ui_bgl.draw_image(x + isizex - gsize - textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
|
|
# gsize, gsize, gravatar, 1)
|
|
ui_bgl.draw_image(x + isizex / 2 + textmargin, y - isizey + texth - gsize - nameline_height - textmargin,
|
|
gsize, gsize, gravatar, 1)
|
|
|
|
i = 0
|
|
column_lines = -1 # start minus one for the name
|
|
xtext = x + textmargin
|
|
fsize = name_height
|
|
tcol = textcol
|
|
|
|
for l in lines:
|
|
ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
|
|
if i == 0:
|
|
ytext = y - name_height + 5 - isizey + texth - textmargin
|
|
elif i == len(lines) - 1:
|
|
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
|
|
tcol = textcol
|
|
tsize = font_height
|
|
else:
|
|
if l[:4] == 'Tip:':
|
|
tcol = textcol_strong
|
|
fsize = font_height
|
|
i += 1
|
|
column_lines += 1
|
|
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
|
|
xtext += int(isizex / ncolumns)
|
|
|
|
column_lines = 1
|
|
for l in alines:
|
|
if gravatar is not None:
|
|
if column_lines == 1:
|
|
xtext += gsize + textmargin
|
|
if column_lines == 4:
|
|
xtext -= gsize + textmargin
|
|
|
|
ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
|
|
if i == 0:
|
|
ytext = y - name_height + 5 - isizey + texth - textmargin
|
|
elif i == len(lines) - 1:
|
|
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
|
|
tcol = textcol
|
|
tsize = font_height
|
|
else:
|
|
if l[:4] == 'Tip:':
|
|
tcol = textcol_strong
|
|
fsize = font_height
|
|
i += 1
|
|
column_lines += 1
|
|
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
|
|
|
|
# for l in lines:
|
|
# if column_lines >= nlines:
|
|
# xtext += int(isizex / ncolumns)
|
|
# column_lines = 0
|
|
# ytext = y - column_lines * line_height - nameline_height - ttipmargin - textmargin - isizey + texth
|
|
# if i == 0:
|
|
# ytext = y - name_height + 5 - isizey + texth - textmargin
|
|
# elif i == len(lines) - 1:
|
|
# ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin * 2 - isizey + texth
|
|
# tcol = textcol
|
|
# tsize = font_height
|
|
# else:
|
|
# if l[:4] == 'Tip:':
|
|
# tcol = textcol_strong
|
|
# fsize = font_height
|
|
# i += 1
|
|
# column_lines += 1
|
|
# ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
|
|
|
|
t = time.time()
|
|
|
|
|
|
def draw_tooltip_old(x, y, text='', author='', img=None):
|
|
region = bpy.context.region
|
|
scale = bpy.context.preferences.view.ui_scale
|
|
t = time.time()
|
|
|
|
ttipmargin = 10
|
|
|
|
font_height = int(12 * scale)
|
|
line_height = int(15 * scale)
|
|
nameline_height = int(23 * scale)
|
|
|
|
lines = text.split('\n')
|
|
ncolumns = 2
|
|
nlines = math.ceil((len(lines) - 1) / ncolumns)
|
|
texth = line_height * nlines + nameline_height
|
|
|
|
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
|
|
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
|
|
|
|
estimated_height = texth + 3 * ttipmargin + isizey
|
|
|
|
if estimated_height > y:
|
|
scaledown = y / (estimated_height)
|
|
scale *= scaledown
|
|
# we need to scale these down to have correct size if the tooltip wouldn't fit.
|
|
font_height = int(12 * scale)
|
|
line_height = int(15 * scale)
|
|
nameline_height = int(23 * scale)
|
|
|
|
lines = text.split('\n')
|
|
ncolumns = 2
|
|
nlines = math.ceil((len(lines) - 1) / ncolumns)
|
|
texth = line_height * nlines + nameline_height
|
|
|
|
isizex = int(512 * scale * img.size[0] / max(img.size[0], img.size[1]))
|
|
isizey = int(512 * scale * img.size[1] / max(img.size[0], img.size[1]))
|
|
|
|
name_height = int(18 * scale)
|
|
|
|
x += 2 * ttipmargin
|
|
y -= 2 * ttipmargin
|
|
|
|
width = isizex + 2 * ttipmargin
|
|
|
|
properties_width = 0
|
|
for r in bpy.context.area.regions:
|
|
if r.type == 'UI':
|
|
properties_width = r.width
|
|
|
|
x = min(x + width, region.width - properties_width) - width
|
|
|
|
bgcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.inner
|
|
textcol = bpy.context.preferences.themes[0].user_interface.wcol_tooltip.text
|
|
textcol = (textcol[0], textcol[1], textcol[2], 1)
|
|
textcol1 = (textcol[0] * .8, textcol[1] * .8, textcol[2] * .8, 1)
|
|
white = (1, 1, 1, .1)
|
|
|
|
ui_bgl.draw_rect(x - ttipmargin,
|
|
y - texth - 2 * ttipmargin - isizey,
|
|
isizex + ttipmargin * 2,
|
|
texth + 3 * ttipmargin + isizey,
|
|
bgcol)
|
|
|
|
i = 0
|
|
column_lines = -1 # start minus one for the name
|
|
xtext = x
|
|
fsize = name_height
|
|
tcol = textcol
|
|
for l in lines:
|
|
if column_lines >= nlines:
|
|
xtext += int(isizex / ncolumns)
|
|
column_lines = 0
|
|
ytext = y - column_lines * line_height - nameline_height - ttipmargin
|
|
if i == 0:
|
|
ytext = y - name_height + 5
|
|
elif i == len(lines) - 1:
|
|
ytext = y - (nlines - 1) * line_height - nameline_height - ttipmargin
|
|
tcol = textcol
|
|
tsize = font_height
|
|
else:
|
|
if l[:5] == 'tags:':
|
|
tcol = textcol1
|
|
fsize = font_height
|
|
i += 1
|
|
column_lines += 1
|
|
ui_bgl.draw_text(l, xtext, ytext, fsize, tcol)
|
|
t = time.time()
|
|
ui_bgl.draw_image(x, y - texth - isizey - ttipmargin, isizex, isizey, img, 1)
|
|
|
|
|
|
def draw_callback_2d(self, context):
|
|
if not utils.guard_from_crash():
|
|
return;
|
|
|
|
a = context.area
|
|
w = context.window
|
|
try:
|
|
# self.area might throw error just by itself.
|
|
a1 = self.area
|
|
w1 = self.window
|
|
go = True
|
|
if len(a.spaces[0].region_quadviews) > 0:
|
|
# print(dir(bpy.context.region_data))
|
|
# print('quad', a.spaces[0].region_3d, a.spaces[0].region_quadviews[0])
|
|
if a.spaces[0].region_3d != context.region_data:
|
|
go = False
|
|
except:
|
|
# bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
|
|
# bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
|
|
go = False
|
|
if go and a == a1 and w == w1:
|
|
|
|
props = context.scene.blenderkitUI
|
|
if props.down_up == 'SEARCH':
|
|
draw_ratings_bgl()
|
|
draw_callback_2d_search(self, context)
|
|
elif props.down_up == 'UPLOAD':
|
|
draw_callback_2d_upload_preview(self, context)
|
|
|
|
|
|
def draw_downloader(x, y, percent=0, img=None):
|
|
if img is not None:
|
|
ui_bgl.draw_image(x, y, 50, 50, img, .5)
|
|
ui_bgl.draw_rect(x, y, 50, int(0.5 * percent), (.2, 1, .2, .3))
|
|
ui_bgl.draw_rect(x - 3, y - 3, 6, 6, (1, 0, 0, .3))
|
|
|
|
|
|
def draw_progress(x, y, text='', percent=None, color=colors.GREEN):
|
|
ui_bgl.draw_rect(x, y, percent, 5, color)
|
|
ui_bgl.draw_text(text, x, y + 8, 16, color)
|
|
|
|
|
|
def draw_callback_3d_progress(self, context):
|
|
# 'star trek' mode gets here, blocked by now ;)
|
|
for threaddata in download.download_threads:
|
|
asset_data = threaddata[1]
|
|
tcom = threaddata[2]
|
|
if tcom.passargs.get('downloaders'):
|
|
for d in tcom.passargs['downloaders']:
|
|
if asset_data['assetType'] == 'model':
|
|
draw_bbox(d['location'], d['rotation'], asset_data['bbox_min'], asset_data['bbox_max'],
|
|
progress=tcom.progress)
|
|
|
|
|
|
def draw_callback_2d_progress(self, context):
|
|
green = (.2, 1, .2, .3)
|
|
offset = 0
|
|
row_height = 35
|
|
|
|
ui = bpy.context.scene.blenderkitUI
|
|
|
|
x = ui.reports_x
|
|
y = ui.reports_y
|
|
index = 0
|
|
for threaddata in download.download_threads:
|
|
asset_data = threaddata[1]
|
|
tcom = threaddata[2]
|
|
|
|
directory = paths.get_temp_dir('%s_search' % asset_data['assetType'])
|
|
tpath = os.path.join(directory, asset_data['thumbnail_small'])
|
|
img = utils.get_hidden_image(tpath, asset_data['id'])
|
|
|
|
if tcom.passargs.get('downloaders'):
|
|
for d in tcom.passargs['downloaders']:
|
|
|
|
loc = view3d_utils.location_3d_to_region_2d(bpy.context.region, bpy.context.space_data.region_3d,
|
|
d['location'])
|
|
if loc is not None:
|
|
if asset_data['assetType'] == 'model':
|
|
# models now draw with star trek mode, no need to draw percent for the image.
|
|
draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img)
|
|
else:
|
|
draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img)
|
|
|
|
|
|
else:
|
|
draw_progress(x, y - index * 30, text='downloading %s' % asset_data['name'],
|
|
percent=tcom.progress)
|
|
index += 1
|
|
for process in bg_blender.bg_processes:
|
|
tcom = process[1]
|
|
draw_progress(x, y - index * 30, '%s' % tcom.lasttext,
|
|
tcom.progress)
|
|
index += 1
|
|
global reports
|
|
for report in reports:
|
|
report.draw(x, y - index * 30)
|
|
index += 1
|
|
report.fade()
|
|
|
|
|
|
def draw_callback_2d_upload_preview(self, context):
|
|
ui_props = context.scene.blenderkitUI
|
|
|
|
props = utils.get_upload_props()
|
|
if props != None and ui_props.draw_tooltip:
|
|
|
|
if ui_props.asset_type != 'BRUSH':
|
|
ui_props.thumbnail_image = props.thumbnail
|
|
else:
|
|
b = utils.get_active_brush()
|
|
ui_props.thumbnail_image = b.icon_filepath
|
|
|
|
img = utils.get_hidden_image(ui_props.thumbnail_image, 'upload_preview')
|
|
|
|
draw_tooltip(ui_props.bar_x, ui_props.bar_y, text=ui_props.tooltip, img=img)
|
|
|
|
|
|
def draw_callback_2d_search(self, context):
|
|
s = bpy.context.scene
|
|
ui_props = context.scene.blenderkitUI
|
|
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
|
|
|
r = self.region
|
|
# hc = bpy.context.preferences.themes[0].view_3d.space.header
|
|
# hc = bpy.context.preferences.themes[0].user_interface.wcol_menu_back.inner
|
|
# hc = (hc[0], hc[1], hc[2], .2)
|
|
hc = (1, 1, 1, .07)
|
|
# grey1 = (hc.r * .55, hc.g * .55, hc.b * .55, 1)
|
|
grey2 = (hc[0] * .8, hc[1] * .8, hc[2] * .8, .5)
|
|
# grey1 = (hc.r, hc.g, hc.b, 1)
|
|
white = (1, 1, 1, 0.2)
|
|
green = (.2, 1, .2, .7)
|
|
highlight = bpy.context.preferences.themes[0].user_interface.wcol_menu_item.inner_sel
|
|
highlight = (1, 1, 1, .2)
|
|
# highlight = (1, 1, 1, 0.8)
|
|
# background of asset bar
|
|
if not ui_props.dragging:
|
|
search_results = s.get('search results')
|
|
search_results_orig = s.get('search results orig')
|
|
if search_results == None:
|
|
return
|
|
h_draw = min(ui_props.hcount, math.ceil(len(search_results) / ui_props.wcount))
|
|
|
|
if ui_props.wcount > len(search_results):
|
|
bar_width = len(search_results) * (ui_props.thumb_size + ui_props.margin) + ui_props.margin
|
|
else:
|
|
bar_width = ui_props.bar_width
|
|
row_height = ui_props.thumb_size + ui_props.margin
|
|
ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, bar_width,
|
|
ui_props.bar_height, hc)
|
|
|
|
if search_results is not None:
|
|
if ui_props.scrolloffset > 0 or ui_props.wcount * ui_props.hcount < len(search_results):
|
|
ui_props.drawoffset = 35
|
|
else:
|
|
ui_props.drawoffset = 0
|
|
|
|
if ui_props.wcount * ui_props.hcount < len(search_results):
|
|
# arrows
|
|
arrow_y = ui_props.bar_y - int((ui_props.bar_height + ui_props.thumb_size) / 2) + ui_props.margin
|
|
if ui_props.scrolloffset > 0:
|
|
|
|
if ui_props.active_index == -2:
|
|
ui_bgl.draw_rect(ui_props.bar_x, ui_props.bar_y - ui_props.bar_height, 25,
|
|
ui_props.bar_height, highlight)
|
|
img = utils.get_thumbnail('arrow_left.png')
|
|
ui_bgl.draw_image(ui_props.bar_x, arrow_y, 25,
|
|
ui_props.thumb_size,
|
|
img,
|
|
1)
|
|
|
|
if search_results_orig['count'] - ui_props.scrolloffset > (ui_props.wcount * ui_props.hcount) + 1:
|
|
if ui_props.active_index == -1:
|
|
ui_bgl.draw_rect(ui_props.bar_x + ui_props.bar_width - 25,
|
|
ui_props.bar_y - ui_props.bar_height, 25,
|
|
ui_props.bar_height,
|
|
highlight)
|
|
img1 = utils.get_thumbnail('arrow_right.png')
|
|
ui_bgl.draw_image(ui_props.bar_x + ui_props.bar_width - 25,
|
|
arrow_y, 25,
|
|
ui_props.thumb_size, img1, 1)
|
|
|
|
for b in range(0, h_draw):
|
|
w_draw = min(ui_props.wcount, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)
|
|
|
|
y = ui_props.bar_y - (b + 1) * (row_height)
|
|
for a in range(0, w_draw):
|
|
x = ui_props.bar_x + a * (
|
|
ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset
|
|
|
|
#
|
|
index = a + ui_props.scrolloffset + b * ui_props.wcount
|
|
iname = utils.previmg_name(index)
|
|
img = bpy.data.images.get(iname)
|
|
if img is not None:
|
|
w = int(ui_props.thumb_size * img.size[0] / max(img.size[0], img.size[1]))
|
|
h = int(ui_props.thumb_size * img.size[1] / max(img.size[0], img.size[1]))
|
|
crop = (0, 0, 1, 1)
|
|
if img.size[0] > img.size[1]:
|
|
offset = (1 - img.size[1] / img.size[0]) / 2
|
|
crop = (offset, 0, 1 - offset, 1)
|
|
|
|
ui_bgl.draw_image(x, y, w, w, img, 1,
|
|
crop=crop)
|
|
if index == ui_props.active_index:
|
|
ui_bgl.draw_rect(x - ui_props.highlight_margin, y - ui_props.highlight_margin,
|
|
w + 2 * ui_props.highlight_margin, w + 2 * ui_props.highlight_margin,
|
|
highlight)
|
|
# if index == ui_props.active_index:
|
|
# ui_bgl.draw_rect(x - highlight_margin, y - highlight_margin,
|
|
# w + 2*highlight_margin, h + 2*highlight_margin , highlight)
|
|
|
|
else:
|
|
ui_bgl.draw_rect(x, y, ui_props.thumb_size, ui_props.thumb_size, white)
|
|
|
|
result = search_results[index]
|
|
if result['downloaded'] > 0:
|
|
ui_bgl.draw_rect(x, y - 2, int(w * result['downloaded'] / 100.0), 2, green)
|
|
# object type icons - just a test..., adds clutter/ not so userfull:
|
|
# icons = ('type_finished.png', 'type_template.png', 'type_particle_system.png')
|
|
|
|
if (result.get('canDownload', True)) == 0:
|
|
img = utils.get_thumbnail('locked.png')
|
|
ui_bgl.draw_image(x + 2, y + 2, 24, 24, img, 1)
|
|
|
|
v_icon = verification_icons[result.get('verificationStatus', 'validated')]
|
|
if v_icon is not None:
|
|
img = utils.get_thumbnail(v_icon)
|
|
ui_bgl.draw_image(x + ui_props.thumb_size - 26, y + 2, 24, 24, img, 1)
|
|
|
|
# if user_preferences.api_key == '':
|
|
# report = 'Register on BlenderKit website to upload your own assets.'
|
|
# ui_bgl.draw_text(report, ui_props.bar_x + ui_props.margin,
|
|
# ui_props.bar_y - 25 - ui_props.margin - ui_props.bar_height, 15)
|
|
# elif len(search_results) == 0:
|
|
# 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)
|
|
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'])
|
|
|
|
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)
|
|
img.colorspace_settings.name = 'sRGB'
|
|
|
|
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:
|
|
iname = utils.previmg_name(ui_props.active_index)
|
|
img = bpy.data.images.get(iname)
|
|
linelength = 35
|
|
ui_bgl.draw_image(ui_props.mouse_x + linelength, ui_props.mouse_y - linelength - ui_props.thumb_size,
|
|
ui_props.thumb_size, ui_props.thumb_size, img, 1)
|
|
ui_bgl.draw_line2d(ui_props.mouse_x, ui_props.mouse_y, ui_props.mouse_x + linelength,
|
|
ui_props.mouse_y - linelength, 2, white)
|
|
|
|
|
|
def draw_callback_3d(self, context):
|
|
''' Draw snapped bbox while dragging and in the future other blenderkit related stuff. '''
|
|
if not utils.guard_from_crash():
|
|
return;
|
|
|
|
ui = context.scene.blenderkitUI
|
|
|
|
if ui.dragging and ui.asset_type == 'MODEL':
|
|
if ui.draw_snapped_bounds:
|
|
draw_bbox(ui.snapped_location, ui.snapped_rotation, ui.snapped_bbox_min, ui.snapped_bbox_max)
|
|
|
|
|
|
def mouse_raycast(context, mx, my):
|
|
r = context.region
|
|
rv3d = context.region_data
|
|
coord = mx, my
|
|
|
|
# get the ray from the viewport and mouse
|
|
view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
|
|
ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord)
|
|
ray_target = ray_origin + (view_vector * 1000000000)
|
|
|
|
vec = ray_target - ray_origin
|
|
|
|
has_hit, snapped_location, snapped_normal, face_index, object, matrix = bpy.context.scene.ray_cast(
|
|
bpy.context.view_layer, ray_origin, vec)
|
|
|
|
# rote = mathutils.Euler((0, 0, math.pi))
|
|
randoffset = math.pi
|
|
if has_hit:
|
|
snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
|
|
up = Vector((0, 0, 1))
|
|
props = bpy.context.scene.blenderkit_models
|
|
if props.randomize_rotation and snapped_normal.angle(up) < math.radians(10.0):
|
|
randoffset = props.offset_rotation_amount + math.pi + (
|
|
random.random() - 0.5) * props.randomize_rotation_amount
|
|
else:
|
|
randoffset = props.offset_rotation_amount # we don't rotate this way on walls and ceilings. + math.pi
|
|
# snapped_rotation.z += math.pi + (random.random() - 0.5) * .2
|
|
|
|
else:
|
|
snapped_rotation = mathutils.Quaternion((0, 0, 0, 0)).to_euler()
|
|
|
|
snapped_rotation.rotate_axis('Z', randoffset)
|
|
|
|
return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix
|
|
|
|
|
|
def floor_raycast(context, mx, my):
|
|
r = context.region
|
|
rv3d = context.region_data
|
|
coord = mx, my
|
|
|
|
# get the ray from the viewport and mouse
|
|
view_vector = view3d_utils.region_2d_to_vector_3d(r, rv3d, coord)
|
|
ray_origin = view3d_utils.region_2d_to_origin_3d(r, rv3d, coord)
|
|
ray_target = ray_origin + (view_vector * 1000)
|
|
|
|
# various intersection plane normals are needed for corner cases that might actually happen quite often - in front and side view.
|
|
# default plane normal is scene floor.
|
|
plane_normal = (0, 0, 1)
|
|
if math.isclose(view_vector.x, 0, abs_tol=1e-4) and math.isclose(view_vector.z, 0, abs_tol=1e-4):
|
|
plane_normal = (0, 1, 0)
|
|
elif math.isclose(view_vector.z, 0, abs_tol=1e-4):
|
|
plane_normal = (1, 0, 0)
|
|
|
|
snapped_location = mathutils.geometry.intersect_line_plane(ray_origin, ray_target, (0, 0, 0), plane_normal,
|
|
False)
|
|
if snapped_location != None:
|
|
has_hit = True
|
|
snapped_normal = Vector((0, 0, 1))
|
|
face_index = None
|
|
object = None
|
|
matrix = None
|
|
snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
|
|
props = bpy.context.scene.blenderkit_models
|
|
if props.randomize_rotation:
|
|
randoffset = props.offset_rotation_amount + math.pi + (
|
|
random.random() - 0.5) * props.randomize_rotation_amount
|
|
else:
|
|
randoffset = props.offset_rotation_amount + math.pi
|
|
snapped_rotation.rotate_axis('Z', randoffset)
|
|
|
|
return has_hit, snapped_location, snapped_normal, snapped_rotation, face_index, object, matrix
|
|
|
|
|
|
def is_rating_possible():
|
|
ao = bpy.context.active_object
|
|
ui = bpy.context.scene.blenderkitUI
|
|
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
|
#first test if user is logged in.
|
|
if preferences.api_key == '':
|
|
return False, False, None, None
|
|
if bpy.context.scene.get('assets rated') is not None and ui.down_up == 'SEARCH':
|
|
if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
|
|
b = utils.get_active_brush()
|
|
ad = b.get('asset_data')
|
|
if ad is not None:
|
|
rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
|
|
return True, rated, b, ad
|
|
if ao is not None:
|
|
ad = None
|
|
# crawl parents to reach active asset. there could have been parenting so we need to find the first onw
|
|
ao_check = ao
|
|
while ad is None or (ad is None and ao_check.parent is not None):
|
|
ad = ao_check.get('asset_data')
|
|
if ad is not None:
|
|
rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
|
|
# originally hidden for already rated assets
|
|
return True, rated, ao_check, ad
|
|
elif ao_check.parent is not None:
|
|
ao_check = ao_check.parent
|
|
else:
|
|
break;
|
|
|
|
# check also materials
|
|
m = ao.active_material
|
|
if m is not None:
|
|
ad = m.get('asset_data')
|
|
if ad is not None:
|
|
rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
|
|
return True, rated, m, ad
|
|
|
|
# if t>2 and t<2.5:
|
|
# ui_props.rating_on = False
|
|
|
|
return False, False, None, None
|
|
|
|
|
|
def interact_rating(r, mx, my, event):
|
|
ui = bpy.context.scene.blenderkitUI
|
|
rating_possible, rated, asset, asset_data = is_rating_possible()
|
|
if rating_possible:
|
|
bkit_ratings = asset.bkit_ratings
|
|
|
|
t = time.time() - ui.last_rating_time
|
|
if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
|
|
accept_value = 'PRESS'
|
|
else:
|
|
accept_value = 'RELEASE'
|
|
|
|
if ui.rating_button_on and event.type == 'LEFTMOUSE' and event.value == accept_value:
|
|
if mouse_in_area(mx, my,
|
|
ui.rating_x,
|
|
ui.rating_y - ui.rating_button_width,
|
|
ui.rating_button_width * 2,
|
|
ui.rating_button_width):
|
|
ui.rating_menu_on = True
|
|
ui.rating_button_on = False
|
|
return True
|
|
if ui.rating_menu_on:
|
|
if mouse_in_area(mx, my,
|
|
ui.rating_x,
|
|
ui.rating_y - ui.rating_ui_height,
|
|
ui.rating_ui_width,
|
|
ui.rating_ui_height + 25):
|
|
rmx = mx - (ui.rating_x)
|
|
rmy = my - (ui.rating_y - ui.rating_ui_height)
|
|
|
|
# quality
|
|
upload_rating = False
|
|
if (ui.quality_stars_x < rmx and rmx < ui.quality_stars_x + 10 * ui.star_size and \
|
|
ui.quality_stars_y < rmy and rmy < ui.quality_stars_y + ui.star_size and event.type == 'LEFTMOUSE' and event.value == 'PRESS') or \
|
|
ui.dragging_rating_quality:
|
|
|
|
if event.type == 'LEFTMOUSE':
|
|
if event.value == 'PRESS':
|
|
ui.dragging_rating = True
|
|
ui.dragging_rating_quality = True
|
|
elif event.value == 'RELEASE':
|
|
ui.dragging_rating = False
|
|
ui.dragging_rating_quality = False
|
|
|
|
if ui.dragging_rating_quality:
|
|
q = math.ceil((rmx - ui.quality_stars_x) / (float(ui.star_size)))
|
|
bkit_ratings.rating_quality = q
|
|
|
|
# work hours
|
|
if (
|
|
ui.workhours_bar_x < rmx and rmx < ui.workhours_bar_x + ui.workhours_bar_x_max + ui.workhours_bar_slider_size and \
|
|
ui.workhours_bar_y < rmy and rmy < ui.workhours_bar_y + ui.workhours_bar_slider_size and event.type == 'LEFTMOUSE' and event.value == 'PRESS') \
|
|
or (ui.dragging_rating_work_hours):
|
|
if event.value == 'PRESS':
|
|
ui.dragging_rating = True
|
|
ui.dragging_rating_work_hours = True
|
|
elif event.value == 'RELEASE':
|
|
ui.dragging_rating = False
|
|
ui.dragging_rating_work_hours = False
|
|
if ui.dragging_rating_work_hours:
|
|
xv = rmx - ui.workhours_bar_x - ui.workhours_bar_slider_size / 2
|
|
ratio = xv / ui.workhours_bar_x_max
|
|
if asset_data['assetType'] == 'model':
|
|
wh_log2 = ratio * 9 - 1
|
|
wh = 2 ** wh_log2
|
|
else:
|
|
wh = 5 * ratio
|
|
bkit_ratings.rating_work_hours = wh
|
|
|
|
if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
|
|
ui.last_rating_time = time.time() # this prop seems obsolete now?
|
|
return True
|
|
else:
|
|
ui.rating_button_on = True
|
|
ui.rating_menu_on = False
|
|
return False
|
|
|
|
|
|
def mouse_in_area(mx, my, x, y, w, h):
|
|
if x < mx < x + w and y < my < y + h:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def mouse_in_asset_bar(mx, my):
|
|
s = bpy.context.scene
|
|
|
|
ui_props = bpy.context.scene.blenderkitUI
|
|
# search_results = s.get('search results')
|
|
# if search_results == None:
|
|
# return False
|
|
#
|
|
# w_draw1 = min(ui_props.wcount + 1, len(search_results) - b * ui_props.wcount - ui_props.scrolloffset)
|
|
# end = ui_props.bar_x + (w_draw1) * (
|
|
# ui_props.margin + ui_props.thumb_size) + ui_props.margin + ui_props.drawoffset + 25
|
|
|
|
if ui_props.bar_y - ui_props.bar_height < my < ui_props.bar_y \
|
|
and mx > ui_props.bar_x and mx < ui_props.bar_x + ui_props.bar_width:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def mouse_in_region(r, mx, my):
|
|
if 0 < my < r.height and 0 < mx < r.width:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def update_ui_size(area, region):
|
|
ui = bpy.context.scene.blenderkitUI
|
|
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
|
ui_scale = bpy.context.preferences.view.ui_scale
|
|
|
|
ui.margin = ui.bl_rna.properties['margin'].default * ui_scale
|
|
ui.thumb_size = user_preferences.thumb_size * 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':
|
|
ui.bar_x = r.width * reg_multiplier + ui.margin + ui.bar_x_offset * ui_scale
|
|
elif r.type == 'UI':
|
|
ui.bar_end = r.width * reg_multiplier + 100 * ui_scale
|
|
|
|
ui.bar_width = region.width - ui.bar_x - ui.bar_end
|
|
ui.wcount = math.floor(
|
|
(ui.bar_width - 2 * ui.drawoffset) / (ui.thumb_size + ui.margin))
|
|
|
|
search_results = bpy.context.scene.get('search results')
|
|
if search_results != None and ui.wcount > 0:
|
|
ui.hcount = min(user_preferences.max_assetbar_rows, math.ceil(len(search_results) / ui.wcount))
|
|
else:
|
|
ui.hcount = 1
|
|
ui.bar_height = (ui.thumb_size + ui.margin) * ui.hcount + ui.margin
|
|
ui.bar_y = region.height - ui.bar_y_offset * ui_scale
|
|
if ui.down_up == 'UPLOAD':
|
|
ui.reports_y = ui.bar_y - 600
|
|
ui.reports_x = ui.bar_x
|
|
else:
|
|
ui.reports_y = ui.bar_y - ui.bar_height - 100
|
|
ui.reports_x = ui.bar_x
|
|
|
|
ui.rating_x = ui.bar_x
|
|
ui.rating_y = ui.bar_y - ui.bar_height
|
|
|
|
|
|
def get_largest_3dview():
|
|
maxsurf = 0
|
|
maxa = None
|
|
maxw = None
|
|
region = None
|
|
for w in bpy.context.window_manager.windows:
|
|
screen = w.screen
|
|
for a in screen.areas:
|
|
if a.type == 'VIEW_3D':
|
|
asurf = a.width * a.height
|
|
if asurf > maxsurf:
|
|
maxa = a
|
|
maxw = w
|
|
maxsurf = asurf
|
|
|
|
for r in a.regions:
|
|
if r.type == 'WINDOW':
|
|
region = r
|
|
global active_area, active_window, active_region
|
|
active_window = maxw
|
|
active_area = maxa
|
|
active_region = region
|
|
return maxw, maxa, region
|
|
|
|
|
|
class AssetBarOperator(bpy.types.Operator):
|
|
'''runs search and displays the asset bar at the same time'''
|
|
bl_idname = "view3d.blenderkit_asset_bar"
|
|
bl_label = "BlenderKit Asset Bar UI"
|
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
|
|
|
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 Only", 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 search_more(self):
|
|
sro = bpy.context.scene.get('search results orig')
|
|
if sro is not None and sro.get('next') is not None:
|
|
search.search(get_next=True)
|
|
|
|
def exit_modal(self):
|
|
try:
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle_2d, 'WINDOW')
|
|
bpy.types.SpaceView3D.draw_handler_remove(self._handle_3d, 'WINDOW')
|
|
except:
|
|
pass;
|
|
ui_props = bpy.context.scene.blenderkitUI
|
|
|
|
ui_props.dragging = False
|
|
ui_props.tooltip = ''
|
|
ui_props.active_index = -3
|
|
ui_props.draw_drag_image = False
|
|
ui_props.draw_snapped_bounds = False
|
|
ui_props.has_hit = False
|
|
ui_props.assetbar_on = False
|
|
|
|
def modal(self, context, event):
|
|
# This is for case of closing the area or changing type:
|
|
ui_props = context.scene.blenderkitUI
|
|
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
|
|
|
areas = []
|
|
|
|
|
|
#timers testing - seems timers might be causing crashes. testing it this way now.
|
|
if not user_preferences.use_timers:
|
|
search.timer_update()
|
|
download.timer_update()
|
|
tasks_queue.queue_worker()
|
|
bg_blender.bg_update()
|
|
|
|
if bpy.context.scene != self.scene:
|
|
self.exit_modal()
|
|
return {'CANCELLED'}
|
|
|
|
for w in context.window_manager.windows:
|
|
areas.extend(w.screen.areas)
|
|
|
|
if self.area not in areas or self.area.type != 'VIEW_3D' or self.has_quad_views != (
|
|
len(self.area.spaces[0].region_quadviews) > 0):
|
|
# print('search areas') bpy.context.area.spaces[0].region_quadviews
|
|
# stopping here model by now - because of:
|
|
# switching layouts or maximizing area now fails to assign new area throwing the bug
|
|
# internal error: modal gizmo-map handler has invalid area
|
|
self.exit_modal()
|
|
return {'CANCELLED'}
|
|
|
|
newarea = None
|
|
for a in context.window.screen.areas:
|
|
if a.type == 'VIEW_3D':
|
|
self.area = a
|
|
for r in a.regions:
|
|
if r.type == 'WINDOW':
|
|
self.region = r
|
|
newarea = a
|
|
break;
|
|
# context.area = a
|
|
|
|
# we check again and quit if things weren't fixed this way.
|
|
if newarea == None:
|
|
self.exit_modal()
|
|
return {'CANCELLED'}
|
|
|
|
update_ui_size(self.area, self.region)
|
|
|
|
|
|
|
|
# this was here to check if sculpt stroke is running, but obviously that didn't help,
|
|
# since the RELEASE event is cought by operator and thus there is no way to detect a stroke has ended...
|
|
if bpy.context.mode in ('SCULPT', 'PAINT_TEXTURE'):
|
|
if event.type == 'MOUSEMOVE': # ASSUME THAT SCULPT OPERATOR ACTUALLY STEALS THESE EVENTS,
|
|
# SO WHEN THERE ARE SOME WE CAN APPEND BRUSH...
|
|
bpy.context.window_manager['appendable'] = True
|
|
if event.type == 'LEFTMOUSE':
|
|
if event.value == 'PRESS':
|
|
bpy.context.window_manager['appendable'] = False
|
|
|
|
self.area.tag_redraw()
|
|
s = context.scene
|
|
|
|
if ui_props.turn_off:
|
|
ui_props.turn_off = False
|
|
self.exit_modal()
|
|
ui_props.draw_tooltip = False
|
|
return {'CANCELLED'}
|
|
|
|
if context.region != self.region:
|
|
# print(time.time(), 'pass through because of region')
|
|
# print(context.region.type, self.region.type)
|
|
return {'PASS_THROUGH'}
|
|
|
|
if ui_props.down_up == 'UPLOAD':
|
|
|
|
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
|
|
if (
|
|
event.type == 'LEFTMOUSE' or event.type == 'RIGHTMOUSE') and event.value == 'RELEASE' or event.type == 'ENTER' or ui_props.tooltip == '':
|
|
ao = bpy.context.active_object
|
|
if ui_props.asset_type == 'MODEL' and ao != None \
|
|
or ui_props.asset_type == 'MATERIAL' and ao != None and ao.active_material != None \
|
|
or ui_props.asset_type == 'BRUSH' and utils.get_active_brush() is not None:
|
|
export_data, upload_data, eval_path_computing, eval_path_state, eval_path, props = upload.get_upload_data(
|
|
self,
|
|
context,
|
|
ui_props.asset_type)
|
|
ui_props.tooltip = search.generate_tooltip(upload_data)
|
|
|
|
return {'PASS_THROUGH'}
|
|
|
|
# TODO add one more condition here to take less performance.
|
|
r = self.region
|
|
s = bpy.context.scene
|
|
sr = s.get('search results')
|
|
search_results_orig = s.get('search results orig')
|
|
# If there aren't any results, we need no interaction(yet)
|
|
if sr is None:
|
|
return {'PASS_THROUGH'}
|
|
if len(sr) - ui_props.scrolloffset < (ui_props.wcount * ui_props.hcount) + 10:
|
|
self.search_more()
|
|
if event.type == 'WHEELUPMOUSE' or event.type == 'WHEELDOWNMOUSE' or event.type == 'TRACKPADPAN':
|
|
# scrolling
|
|
mx = event.mouse_region_x
|
|
my = event.mouse_region_y
|
|
|
|
if ui_props.dragging and not mouse_in_asset_bar(mx, my): # and my < r.height - ui_props.bar_height \
|
|
# and mx > 0 and mx < r.width and my > 0:
|
|
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.
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
|
|
context, mx, my)
|
|
|
|
# MODELS can be dragged on scene floor
|
|
if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
|
|
context,
|
|
mx, my)
|
|
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if not mouse_in_asset_bar(mx, my):
|
|
return {'PASS_THROUGH'}
|
|
|
|
# note - TRACKPADPAN is unsupported in blender by now.
|
|
# if event.type == 'TRACKPADPAN' :
|
|
# print(dir(event))
|
|
# print(event.value, event.oskey, event.)
|
|
if (event.type == 'WHEELDOWNMOUSE') and len(sr) - ui_props.scrolloffset > (
|
|
ui_props.wcount * ui_props.hcount):
|
|
if ui_props.hcount > 1:
|
|
ui_props.scrolloffset += ui_props.wcount
|
|
else:
|
|
ui_props.scrolloffset += 1
|
|
if len(sr) - ui_props.scrolloffset < (ui_props.wcount * ui_props.hcount):
|
|
ui_props.scrolloffset = len(sr) - (ui_props.wcount * ui_props.hcount)
|
|
|
|
if event.type == 'WHEELUPMOUSE' and ui_props.scrolloffset > 0:
|
|
if ui_props.hcount > 1:
|
|
ui_props.scrolloffset -= ui_props.wcount
|
|
else:
|
|
ui_props.scrolloffset -= 1
|
|
if ui_props.scrolloffset < 0:
|
|
ui_props.scrolloffset = 0
|
|
|
|
return {'RUNNING_MODAL'}
|
|
if event.type == 'MOUSEMOVE': # Apply
|
|
|
|
r = self.region
|
|
mx = event.mouse_region_x
|
|
my = event.mouse_region_y
|
|
|
|
ui_props.mouse_x = mx
|
|
ui_props.mouse_y = my
|
|
|
|
if ui_props.dragging_rating or ui_props.rating_menu_on:
|
|
res = interact_rating(r, mx, my, event)
|
|
if res == True:
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if ui_props.drag_init:
|
|
ui_props.drag_length += 1
|
|
if ui_props.drag_length > 0:
|
|
ui_props.dragging = True
|
|
ui_props.drag_init = False
|
|
|
|
if not (ui_props.dragging and mouse_in_region(r, mx, my)) and not mouse_in_asset_bar(mx, my): #
|
|
|
|
ui_props.dragging = False
|
|
ui_props.has_hit = False
|
|
ui_props.active_index = -3
|
|
ui_props.draw_drag_image = False
|
|
ui_props.draw_snapped_bounds = False
|
|
ui_props.draw_tooltip = False
|
|
bpy.context.window.cursor_set("DEFAULT")
|
|
return {'PASS_THROUGH'}
|
|
|
|
sr = bpy.context.scene['search results']
|
|
|
|
if not ui_props.dragging:
|
|
bpy.context.window.cursor_set("DEFAULT")
|
|
|
|
if sr != None and ui_props.wcount * ui_props.hcount > len(sr) and ui_props.scrolloffset > 0:
|
|
ui_props.scrolloffset = 0
|
|
|
|
asset_search_index = get_asset_under_mouse(mx, my)
|
|
ui_props.active_index = asset_search_index
|
|
if asset_search_index > -1:
|
|
|
|
asset_data = sr[asset_search_index]
|
|
ui_props.draw_tooltip = True
|
|
|
|
ui_props.tooltip = asset_data['tooltip']
|
|
# bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
|
|
|
|
else:
|
|
ui_props.draw_tooltip = False
|
|
|
|
if mx > ui_props.bar_x + ui_props.bar_width - 50 and search_results_orig[
|
|
'count'] - ui_props.scrolloffset > (
|
|
ui_props.wcount * ui_props.hcount) + 1:
|
|
ui_props.active_index = -1
|
|
return {'RUNNING_MODAL'}
|
|
if mx < ui_props.bar_x + 50 and ui_props.scrolloffset > 0:
|
|
ui_props.active_index = -2
|
|
return {'RUNNING_MODAL'}
|
|
|
|
else:
|
|
result = False
|
|
if ui_props.dragging and mouse_in_region(r, mx, my):
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
|
|
context, mx, my)
|
|
# MODELS can be dragged on scene floor
|
|
if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
|
|
context,
|
|
mx, my)
|
|
if ui_props.has_hit and ui_props.asset_type == 'MODEL':
|
|
# this condition is here to fix a bug for a scene submitted by a user, so this situation shouldn't
|
|
# happen anymore, but there might exists scenes which have this problem for some reason.
|
|
if ui_props.active_index < len(sr) and ui_props.active_index > -1:
|
|
ui_props.draw_snapped_bounds = True
|
|
active_mod = sr[ui_props.active_index]
|
|
ui_props.snapped_bbox_min = Vector(active_mod['bbox_min'])
|
|
ui_props.snapped_bbox_max = Vector(active_mod['bbox_max'])
|
|
|
|
else:
|
|
ui_props.draw_snapped_bounds = False
|
|
ui_props.draw_drag_image = True
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if event.type == 'RIGHTMOUSE':
|
|
mx = event.mouse_x - r.x
|
|
my = event.mouse_y - r.y
|
|
|
|
if event.value == 'PRESS' and mouse_in_asset_bar(mx, my):
|
|
bpy.ops.wm.call_menu(name='OBJECT_MT_blenderkit_asset_menu')
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if event.type == 'LEFTMOUSE':
|
|
|
|
r = self.region
|
|
mx = event.mouse_x - r.x
|
|
my = event.mouse_y - r.y
|
|
|
|
ui_props = context.scene.blenderkitUI
|
|
if event.value == 'PRESS' and ui_props.active_index > -1:
|
|
if ui_props.asset_type == 'MODEL' or ui_props.asset_type == 'MATERIAL':
|
|
# check if asset is locked and let the user know in that case
|
|
asset_search_index = ui_props.active_index
|
|
asset_data = sr[asset_search_index]
|
|
if not asset_data.get('canDownload'):
|
|
message = "Let's support asset creators and Blender development."
|
|
link_text = 'Unlock the asset.'
|
|
url = paths.get_bkit_url() + '/get-blenderkit/' + asset_data['id'] + '/?from_addon'
|
|
bpy.ops.wm.blenderkit_url_dialog('INVOKE_REGION_WIN', url=url, message=message,
|
|
link_text=link_text)
|
|
return {'RUNNING_MODAL'}
|
|
# go on with drag init
|
|
ui_props.drag_init = True
|
|
bpy.context.window.cursor_set("NONE")
|
|
ui_props.draw_tooltip = False
|
|
ui_props.drag_length = 0
|
|
|
|
if ui_props.rating_on:
|
|
res = interact_rating(r, mx, my, event)
|
|
if res:
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if not ui_props.dragging and not mouse_in_asset_bar(mx, my):
|
|
return {'PASS_THROUGH'}
|
|
|
|
# this can happen by switching result asset types - length of search result changes
|
|
if ui_props.scrolloffset > 0 and (ui_props.wcount * ui_props.hcount) > len(sr) - ui_props.scrolloffset:
|
|
ui_props.scrolloffset = len(sr) - (ui_props.wcount * ui_props.hcount)
|
|
|
|
if event.value == 'RELEASE': # Confirm
|
|
ui_props.drag_init = False
|
|
|
|
# scroll by a whole page
|
|
if mx > ui_props.bar_x + ui_props.bar_width - 50 and len(
|
|
sr) - ui_props.scrolloffset > ui_props.wcount * ui_props.hcount:
|
|
ui_props.scrolloffset = min(
|
|
ui_props.scrolloffset + (ui_props.wcount * ui_props.hcount),
|
|
len(sr) - ui_props.wcount * ui_props.hcount)
|
|
return {'RUNNING_MODAL'}
|
|
if mx < ui_props.bar_x + 50 and ui_props.scrolloffset > 0:
|
|
ui_props.scrolloffset = max(0, ui_props.scrolloffset - ui_props.wcount * ui_props.hcount)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
# Drag-drop interaction
|
|
if ui_props.dragging and mouse_in_region(r, mx, my):
|
|
asset_search_index = ui_props.active_index
|
|
# raycast here
|
|
ui_props.active_index = -3
|
|
|
|
if ui_props.asset_type == 'MODEL':
|
|
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
|
|
context, mx, my)
|
|
|
|
# MODELS can be dragged on scene floor
|
|
if not ui_props.has_hit and ui_props.asset_type == 'MODEL':
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = floor_raycast(
|
|
context,
|
|
mx, my)
|
|
|
|
if not ui_props.has_hit:
|
|
return {'RUNNING_MODAL'}
|
|
|
|
target_object = ''
|
|
if object is not None:
|
|
target_object = object.name
|
|
target_slot = ''
|
|
|
|
if ui_props.asset_type == 'MATERIAL':
|
|
ui_props.has_hit, ui_props.snapped_location, ui_props.snapped_normal, ui_props.snapped_rotation, face_index, object, matrix = mouse_raycast(
|
|
context, mx, my)
|
|
|
|
if not ui_props.has_hit:
|
|
# this is last attempt to get object under mouse - for curves and other objects than mesh.
|
|
ui_props.dragging = False
|
|
sel = utils.selection_get()
|
|
bpy.ops.view3d.select(location=(event.mouse_region_x, event.mouse_region_y))
|
|
sel1 = utils.selection_get()
|
|
if sel[0] != sel1[0] and sel1[0].type != 'MESH':
|
|
object = sel1[0]
|
|
target_slot = sel1[0].active_material_index
|
|
ui_props.has_hit = True
|
|
utils.selection_set(sel)
|
|
|
|
if not ui_props.has_hit:
|
|
return {'RUNNING_MODAL'}
|
|
|
|
else:
|
|
# first, test if object can have material applied.
|
|
# TODO add other types here if droppable.
|
|
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[face_index].material_index
|
|
object_eval.to_mesh_clear()
|
|
else:
|
|
self.report({'WARNING'}, "Invalid or library object as input:")
|
|
target_object = ''
|
|
target_slot = ''
|
|
|
|
# Click interaction
|
|
else:
|
|
asset_search_index = get_asset_under_mouse(mx, my)
|
|
|
|
if ui_props.asset_type in ('MATERIAL',
|
|
'MODEL'): # this was meant for particles, commenting for now or ui_props.asset_type == 'MODEL':
|
|
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
|
|
else:
|
|
target_object = ''
|
|
target_slot = ''
|
|
# FIRST START SEARCH
|
|
|
|
if asset_search_index == -3:
|
|
return {'RUNNING_MODAL'}
|
|
if asset_search_index > -3:
|
|
if ui_props.asset_type == 'MATERIAL':
|
|
if target_object != '':
|
|
# position is for downloader:
|
|
loc = ui_props.snapped_location
|
|
rotation = (0, 0, 0)
|
|
|
|
asset_data = sr[asset_search_index]
|
|
utils.automap(target_object, target_slot=target_slot,
|
|
tex_size=asset_data.get('texture_size_meters', 1.0))
|
|
bpy.ops.scene.blenderkit_download(True,
|
|
asset_type=ui_props.asset_type,
|
|
asset_index=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 ui_props.has_hit and ui_props.dragging:
|
|
loc = ui_props.snapped_location
|
|
rotation = ui_props.snapped_rotation
|
|
else:
|
|
loc = s.cursor.location
|
|
rotation = s.cursor.rotation_euler
|
|
|
|
bpy.ops.scene.blenderkit_download(True,
|
|
asset_type=ui_props.asset_type,
|
|
asset_index=asset_search_index,
|
|
model_location=loc,
|
|
model_rotation=rotation,
|
|
target_object=target_object)
|
|
|
|
else:
|
|
bpy.ops.scene.blenderkit_download(asset_type=ui_props.asset_type,
|
|
asset_index=asset_search_index)
|
|
|
|
ui_props.dragging = False
|
|
return {'RUNNING_MODAL'}
|
|
else:
|
|
return {'RUNNING_MODAL'}
|
|
|
|
if event.type == 'W' and ui_props.active_index > -1:
|
|
sr = bpy.context.scene['search results']
|
|
asset_data = sr[ui_props.active_index]
|
|
a = bpy.context.window_manager['bkit authors'].get(asset_data['author']['id'])
|
|
if a is not None:
|
|
utils.p('author:', a)
|
|
if a.get('aboutMeUrl') is not None:
|
|
bpy.ops.wm.url_open(url=a['aboutMeUrl'])
|
|
return {'RUNNING_MODAL'}
|
|
if event.type == 'A' and ui_props.active_index > -1:
|
|
sr = bpy.context.scene['search results']
|
|
asset_data = sr[ui_props.active_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 {'RUNNING_MODAL'}
|
|
if event.type == 'X' and ui_props.active_index > -1:
|
|
# delete downloaded files for this asset
|
|
sr = bpy.context.scene['search results']
|
|
asset_data = sr[ui_props.active_index]
|
|
print(asset_data['name'])
|
|
print('delete')
|
|
paths.delete_asset_debug(asset_data)
|
|
asset_data['downloaded'] = 0
|
|
return {'RUNNING_MODAL'}
|
|
return {'PASS_THROUGH'}
|
|
|
|
def invoke(self, context, event):
|
|
# FIRST START SEARCH
|
|
ui_props = context.scene.blenderkitUI
|
|
|
|
if self.do_search:
|
|
# 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)
|
|
|
|
if ui_props.assetbar_on:
|
|
# 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 {'FINISHED'}
|
|
|
|
ui_props.dragging = False # only for cases where assetbar ended with an error.
|
|
ui_props.assetbar_on = True
|
|
ui_props.turn_off = False
|
|
|
|
sr = bpy.context.scene.get('search results')
|
|
if sr is None:
|
|
bpy.context.scene['search results'] = []
|
|
|
|
if context.area.type != 'VIEW_3D':
|
|
self.report({'WARNING'}, "View3D not found, cannot run operator")
|
|
return {'CANCELLED'}
|
|
|
|
# the arguments we pass the the callback
|
|
args = (self, context)
|
|
|
|
self.window = context.window
|
|
self.area = context.area
|
|
self.scene = bpy.context.scene
|
|
|
|
self.has_quad_views = len(bpy.context.area.spaces[0].region_quadviews) > 0
|
|
|
|
for r in self.area.regions:
|
|
if r.type == 'WINDOW':
|
|
self.region = r
|
|
|
|
global active_window, active_area, active_region
|
|
active_window = self.window
|
|
active_area = self.area
|
|
active_region = self.region
|
|
|
|
update_ui_size(self.area, self.region)
|
|
|
|
self._handle_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d, args, 'WINDOW', 'POST_PIXEL')
|
|
self._handle_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d, args, 'WINDOW', 'POST_VIEW')
|
|
|
|
context.window_manager.modal_handler_add(self)
|
|
ui_props.assetbar_on = True
|
|
return {'RUNNING_MODAL'}
|
|
|
|
def execute(self, context):
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
class TransferBlenderkitData(bpy.types.Operator):
|
|
"""Regenerate cobweb"""
|
|
bl_idname = "object.blenderkit_data_trasnfer"
|
|
bl_label = "Transfer BlenderKit data"
|
|
bl_description = "Transfer blenderKit metadata from one object to another when fixing uploads with wrong parenting."
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
source_ob = bpy.context.active_object
|
|
for target_ob in bpy.context.selected_objects:
|
|
if target_ob != source_ob:
|
|
target_ob.property_unset('blenderkit')
|
|
for k in source_ob.keys():
|
|
target_ob[k] = source_ob[k]
|
|
source_ob.property_unset('blenderkit')
|
|
return {'FINISHED'}
|
|
|
|
|
|
class UndoWithContext(bpy.types.Operator):
|
|
"""Regenerate cobweb"""
|
|
bl_idname = "wm.undo_push_context"
|
|
bl_label = "BlnenderKit undo push"
|
|
bl_description = "BlenderKit undo push with fixed context"
|
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
|
|
|
# def modal(self, context, event):
|
|
# return {'RUNNING_MODAL'}
|
|
|
|
message: StringProperty('Undo Message', default='BlenderKit operation')
|
|
|
|
def execute(self, context):
|
|
C_dict = bpy.context.copy()
|
|
C_dict.update(region='WINDOW')
|
|
if context.area is None or context.area.type != 'VIEW_3D':
|
|
w, a, r = get_largest_3dview()
|
|
override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
|
|
C_dict.update(override)
|
|
bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message)
|
|
return {'FINISHED'}
|
|
|
|
|
|
class RunAssetBarWithContext(bpy.types.Operator):
|
|
"""Regenerate cobweb"""
|
|
bl_idname = "object.run_assetbar_fix_context"
|
|
bl_label = "BlnenderKit assetbar with fixed context"
|
|
bl_description = "Run assetbar with fixed context"
|
|
bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
|
|
|
|
# def modal(self, context, event):
|
|
# return {'RUNNING_MODAL'}
|
|
|
|
def execute(self, context):
|
|
C_dict = bpy.context.copy()
|
|
C_dict.update(region='WINDOW')
|
|
if context.area is None or context.area.type != 'VIEW_3D':
|
|
w, a, r = get_largest_3dview()
|
|
override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
|
|
C_dict.update(override)
|
|
bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False)
|
|
return {'FINISHED'}
|
|
|
|
|
|
classess = (
|
|
AssetBarOperator,
|
|
RunAssetBarWithContext,
|
|
TransferBlenderkitData,
|
|
UndoWithContext
|
|
)
|
|
|
|
# store keymap items here to access after registration
|
|
addon_keymapitems = []
|
|
|
|
|
|
# @persistent
|
|
def pre_load(context):
|
|
ui_props = bpy.context.scene.blenderkitUI
|
|
ui_props.assetbar_on = False
|
|
ui_props.turn_off = True
|
|
preferences = bpy.context.preferences.addons['blenderkit'].preferences
|
|
preferences.login_attempt = False
|
|
|
|
|
|
def register_ui():
|
|
global handler_2d, handler_3d
|
|
|
|
for c in classess:
|
|
bpy.utils.register_class(c)
|
|
|
|
args = (None, bpy.context)
|
|
|
|
handler_2d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_2d_progress, args, 'WINDOW', 'POST_PIXEL')
|
|
handler_3d = bpy.types.SpaceView3D.draw_handler_add(draw_callback_3d_progress, args, 'WINDOW', 'POST_VIEW')
|
|
|
|
wm = bpy.context.window_manager
|
|
|
|
# spaces solved by registering shortcut to Window. Couldn't register object mode before somehow.
|
|
if not wm.keyconfigs.addon:
|
|
return
|
|
km = wm.keyconfigs.addon.keymaps.new(name="Window", space_type='EMPTY')
|
|
kmi = km.keymap_items.new(AssetBarOperator.bl_idname, 'SEMI_COLON', 'PRESS', ctrl=False, shift=False)
|
|
kmi.properties.keep_running = False
|
|
kmi.properties.do_search = False
|
|
addon_keymapitems.append(kmi)
|
|
# auto open after searching:
|
|
kmi = km.keymap_items.new(RunAssetBarWithContext.bl_idname, 'SEMI_COLON', 'PRESS', \
|
|
ctrl=True, shift=True, alt=True)
|
|
addon_keymapitems.append(kmi)
|
|
|
|
|
|
def unregister_ui():
|
|
global handler_2d, handler_3d
|
|
pre_load(bpy.context)
|
|
|
|
bpy.types.SpaceView3D.draw_handler_remove(handler_2d, 'WINDOW')
|
|
bpy.types.SpaceView3D.draw_handler_remove(handler_3d, 'WINDOW')
|
|
|
|
for c in classess:
|
|
bpy.utils.unregister_class(c)
|
|
|
|
wm = bpy.context.window_manager
|
|
if not wm.keyconfigs.addon:
|
|
return
|
|
|
|
km = wm.keyconfigs.addon.keymaps['Window']
|
|
for kmi in addon_keymapitems:
|
|
km.keymap_items.remove(kmi)
|
|
del addon_keymapitems[:]
|