Newer
Older
# ##### 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)
ui_panels = importlib.reload(ui_panels)
icons = importlib.reload(icons)
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,
IntProperty,
FloatVectorProperty
)
from bpy_extras import view3d_utils
import mathutils
from mathutils import Vector
import time
bk_logger = logging.getLogger('blenderkit')
handler_2d = None
handler_3d = None
active_area_pointer = None
active_window_pointer = None
active_region_pointer = None
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',
'validated': None,
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
}
# 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.as_pointer() == active_area_pointer:
ui_bgl.draw_text(self.text, x, y + 8, 16, self.draw_color)
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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]
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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
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 not img or max(img.size[0], img.size[1]) == 0:
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] * 2.3, textcol[2] * 1.3, 1)
textcol_strong = (0.4, 1, 0.3, 1)
white = (1, 1, 1, .1)
ui_bgl.draw_rect(x - ttipmargin,
y - 2 * ttipmargin - isizey,
isizex + ttipmargin * 2,
2 * ttipmargin + isizey,
bgcol)
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)
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:
fsize = font_height
if l[:4] == 'Tip:' or l[:11] == 'Please rate':
tcol = textcol_strong
fsize = font_height + 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:
fsize = font_height
if l[:4] == 'Tip:' or l[:11] == 'Please rate':
tcol = textcol_strong
fsize = font_height + 1
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):
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
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:
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
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():
Vilém Duha
committed
w = context.window
try:
# self.area might throw error just by itself.
a1 = self.area
Vilém Duha
committed
w1 = self.window
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
Vilém Duha
committed
if go and a == a1 and w == w1:
props = context.scene.blenderkitUI
if props.down_up == 'SEARCH':
draw_ratings_bgl()
elif props.down_up == 'UPLOAD':
draw_callback_2d_upload_preview(self, context)
def draw_downloader(x, y, percent=0, img=None, text=''):
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))
# if asset_data is not None:
# ui_bgl.draw_text(asset_data['name'], x, y, colors.TEXT)
# ui_bgl.draw_text(asset_data['filesSize'])
if text:
ui_bgl.draw_text(text, x, y - 15, 12, colors.TEXT)
# asset_data and asset_data.get('filesSize'):
# fs = asset_data['filesSize']
# fsmb = fs // (1024 * 1024)
# fskb = fs % 1024
# if fsmb == 0:
# t += 'files size: %iKB\n' % fskb
# else:
# t += 'files size: %iMB %iKB\n' % (fsmb, fskb)
def draw_progress(x, y, text='', percent=None, color=colors.GREEN):
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, text=tcom.report)
draw_downloader(loc[0], loc[1], percent=tcom.progress, img=img, text=tcom.report)
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()
# assets which don't need asset preview
if ui_props.asset_type == 'HDR':
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 is_upload_old(asset_data):
'''
estimates if the asset is far too long in the 'uploaded' state
This returns the number of days the validation is over the limit.
'''
date_time_str = asset_data["created"][:10]
# date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d')
today = date_time_obj.today()
age = today - date_time_obj
old = datetime.timedelta(days=7)
if age > old:
return (age.days - old.days)
return 0
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 ui_props.hcount>0:
# #this fixes a draw issue introduced in blender 2.91. draws a very small version of the image to avoid problems
# # with alpha. Not sure why this works.
# img = utils.get_thumbnail('arrow_left.png')
# ui_bgl.draw_image(0, 0, 1,
# 1,
# img,
# 1)
if not ui_props.dragging and ui_props.hcount > 0 and ui_props.wcount > 0:
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
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 and img.size[0] > 0 and img.size[1] > 0:
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)
# code to inform validators that the validation is waiting too long and should be done asap
if result['verificationStatus'] == 'uploaded':
if utils.profile_is_validator():
over_limit = is_upload_old(result)
if over_limit:
red = (1, 0, 0, redness)
ui_bgl.draw_rect(x, y, ui_props.thumb_size, ui_props.thumb_size, red)
ui_bgl.draw_rect(x, y, int(ui_props.thumb_size * 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'])
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:
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():
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.depsgraph, ray_origin, vec)
# rote = mathutils.Euler((0, 0, math.pi))
randoffset = math.pi
if has_hit:
props = bpy.context.scene.blenderkit_models
up = Vector((0, 0, 1))
if props.perpendicular_snap:
if snapped_normal.z > 1 - props.perpendicular_snap_threshold:
snapped_normal = Vector((0, 0, 1))
elif snapped_normal.z < -1 + props.perpendicular_snap_threshold:
snapped_normal = Vector((0, 0, -1))
elif abs(snapped_normal.z) < props.perpendicular_snap_threshold:
snapped_normal.z = 0
snapped_normal.normalize()
snapped_rotation = snapped_normal.to_track_quat('Z', 'Y').to_euler()
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
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
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:
# 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 and ad.get('assetBaseId') is not None:
rated = s['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:
# check also materials
m = ao.active_material
if m is not None:
ad = m.get('asset_data')
rated = bpy.context.scene['assets rated'].get(ad['assetBaseId'])
# 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
ctx = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
bpy.ops.wm.blenderkit_menu_rating_upload(ctx, 'INVOKE_DEFAULT', asset_name=asset_data['name'],
asset_id=asset_data['id'],
return True
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
# 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):
if bpy.app.background or not area:
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 - 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
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
class ParticlesDropDialog(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.blenderkit_particles_drop"
bl_label = "BlenderKit particle plants object drop"
bl_options = {'REGISTER', 'INTERNAL'}
asset_search_index: IntProperty(name="Asset index",
description="Index of the asset in asset bar",
default=0,
)
model_location: FloatVectorProperty(name="Location",
default=(0, 0, 0))
model_rotation: FloatVectorProperty(name="Rotation",
default=(0, 0, 0),
subtype='QUATERNION')
target_object: StringProperty(
name="Target object",
description="The object to which the particles will get applied",
default="", options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return True
def draw(self, context):
layout = self.layout
message = 'This asset is a particle setup. BlenderKit can apply particles to the active/drag-drop object.' \
'The number of particles is caluclated automatically, but if there are 2 many particles,' \
' BlenderKit can do the following steps to make sure Blender continues to run:' \
'\n1.Switch to bounding box view of the particles.' \
'\n2.Turn down number of particles that are shown in the view.' \
'\n3.Hide the particle system completely from the 3D view.' \
"as a result of this, it's possible you'll see the particle setup only in render view or " \
"rendered images. You should still be careful and test particle systems on smaller objects first."
utils.label_multiline(layout, text=message, width=400)
def execute(self, context):
bpy.ops.scene.blenderkit_download(True,
# asset_type=ui_props.asset_type,
asset_index=self.asset_search_index,
model_location=self.model_rotation,
model_rotation=self.model_rotation,
target_object=self.target_object)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=400)
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"
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
sro = bpy.context.scene.get('search results orig')
if sro is not None and sro.get('next') is not None:
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
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
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
# 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 \
or ui_props.asset_type == 'SCENE' or ui_props.asset_type == 'HDR':
export_data, upload_data = upload.get_upload_data(context=context, asset_type=ui_props.asset_type)
if upload_data:
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')
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
# 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.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')
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
Vilém Duha
committed
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 Open source."
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
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
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
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
# change snapped location for placing material downloader.
ui_props.snapped_location = bpy.context.active_object.location
else:
target_object = ''
target_slot = ''
# FIRST START SEARCH
if asset_search_index == -3:
return {'RUNNING_MODAL'}
asset_data = sr[asset_search_index]
if ui_props.asset_type == 'MATERIAL':
if target_object != '':
# position is for downloader:
loc = ui_props.snapped_location
rotation = (0, 0, 0)
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_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
if 'particle_plants' in asset_data['tags']:
bpy.ops.object.blenderkit_particles_drop("INVOKE_DEFAULT",
asset_search_index=asset_search_index,
model_location=loc,
model_rotation=rotation,
target_object=target_object)
else:
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)
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'}
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'])
utils.p('author:', a)
if a.get('aboutMeUrl') is not None:
bpy.ops.wm.url_open(url=a['aboutMeUrl'])
return {'RUNNING_MODAL'}
sr = bpy.context.scene['search results']
asset_data = sr[ui_props.active_index]
a = asset_data['author']['id']
sprops = utils.get_search_props()
sprops.search_keywords = ''
sprops.search_verification_status = 'ALL'
return {'RUNNING_MODAL'}
# delete downloaded files for this asset
sr = bpy.context.scene['search results']
asset_data = sr[ui_props.active_index]
bk_logger.info('delete asset from local drive:' + asset_data['name'])
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_pointer, active_area_pointer, active_region_pointer
active_window_pointer = self.window.as_pointer()
active_area_pointer = self.area.as_pointer()
active_region_pointer = self.region.as_pointer()
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 = utils.get_fake_context(context)
# wm = bpy.context.window_manager#bpy.data.window_managers[0]
# w = wm.windows[0]
#
# C_dict = {'window': w, 'screen': w.screen}
# bpy.ops.ed.undo_push(C_dict, 'INVOKE_REGION_WIN', message=self.message)
bpy.ops.ed.undo_push('INVOKE_REGION_WIN', message=self.message)
return {'FINISHED'}
Vilém Duha
committed
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'}
Vilém Duha
committed
# def modal(self, context, event):
# return {'RUNNING_MODAL'}
def execute(self, context):
C_dict = utils.get_fake_context(context)
if C_dict.get('window'): # no 3d view, no asset bar.
Vilém Duha
committed
bpy.ops.view3d.blenderkit_asset_bar(C_dict, 'INVOKE_REGION_WIN', keep_running=True, do_search=False)
# AssetBarExperiment,
Vilém Duha
committed
RunAssetBarWithContext,
UndoWithContext,
ParticlesDropDialog
# store keymap items here to access after registration
addon_keymapitems = []
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
global handler_2d, handler_3d
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
Vilém Duha
committed
addon_keymapitems.append(kmi)
wm = bpy.context.window_manager
km = wm.keyconfigs.addon.keymaps['Window']
kmi = km.keymap_items.new(ratings.FastRateMenu.bl_idname, 'F', 'PRESS', ctrl=False, shift=False)
kmi = km.keymap_items.new(upload.FastMetadata.bl_idname, 'F', 'PRESS', ctrl=True, shift=False)
addon_keymapitems.append(kmi)
global handler_2d, handler_3d
bpy.types.SpaceView3D.draw_handler_remove(handler_2d, 'WINDOW')
bpy.types.SpaceView3D.draw_handler_remove(handler_3d, 'WINDOW')
bpy.utils.unregister_class(c)
wm = bpy.context.window_manager
if not wm.keyconfigs.addon:
return
km = wm.keyconfigs.addon.keymaps.get('Window')
if km:
for kmi in addon_keymapitems:
km.keymap_items.remove(kmi)
del addon_keymapitems[:]