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 #####
import bpy
from mathutils import Vector
import json
import os
ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000
BELOW_NORMAL_PRIORITY_CLASS = 0x00004000
HIGH_PRIORITY_CLASS = 0x00000080
IDLE_PRIORITY_CLASS = 0x00000040
NORMAL_PRIORITY_CLASS = 0x00000020
REALTIME_PRIORITY_CLASS = 0x00000100
def get_process_flags():
flags = BELOW_NORMAL_PRIORITY_CLASS
if sys.platform != 'win32': # TODO test this on windows
flags = 0
return flags
def activate(ob):
bpy.ops.object.select_all(action='DESELECT')
ob.select_set(True)
bpy.context.view_layer.objects.active = ob
def selection_get():
aob = bpy.context.view_layer.objects.active
selobs = bpy.context.view_layer.objects.selected[:]
return (aob, selobs)
def selection_set(sel):
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = sel[0]
for ob in sel[1]:
ob.select_set(True)
def get_active_model():
if bpy.context.view_layer.objects.active is not None:
ob = bpy.context.view_layer.objects.active
while ob.parent is not None:
ob = ob.parent
return ob
return None
def get_selected_models():
'''
Detect all hierarchies that contain asset data from selection. Only parents that have actual ['asset data'] get returned
Returns
list of objects containing asset data.
'''
obs = bpy.context.selected_objects[:]
done = {}
parents = []
for ob in obs:
if ob not in done:
while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
done[ob] = True
ob = ob.parent
if ob not in parents and ob not in done:
if ob.blenderkit.name != '' or ob.instance_collection is not None:
# if no blenderkit - like objects were found, use the original selection.
if len(parents) == 0:
parents = obs
def get_selected_replace_adepts():
'''
Detect all hierarchies that contain either asset data from selection, or selected objects themselves.
Returns
list of objects for replacement.
'''
obs = bpy.context.selected_objects[:]
done = {}
parents = []
for selected_ob in obs:
ob = selected_ob
if ob not in done:
while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
done[ob] = True
# print('step,',ob.name)
ob = ob.parent
# print('fin', ob.name)
if ob not in parents and ob not in done:
if ob.blenderkit.name != '' or ob.instance_collection is not None:
parents.append(ob)
done[ob] = True
# print(parents)
# if no blenderkit - like objects were found, use the original selection.
if len(parents) == 0:
parents = obs
def get_search_props():
scene = bpy.context.scene
if scene is None:
return;
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
uiprops = scene.blenderkitUI
props = None
if uiprops.asset_type == 'MODEL':
if not hasattr(scene, 'blenderkit_models'):
return;
props = scene.blenderkit_models
if uiprops.asset_type == 'SCENE':
if not hasattr(scene, 'blenderkit_scene'):
return;
props = scene.blenderkit_scene
if uiprops.asset_type == 'MATERIAL':
if not hasattr(scene, 'blenderkit_mat'):
return;
props = scene.blenderkit_mat
if uiprops.asset_type == 'TEXTURE':
if not hasattr(scene, 'blenderkit_tex'):
return;
# props = scene.blenderkit_tex
if uiprops.asset_type == 'BRUSH':
if not hasattr(scene, 'blenderkit_brush'):
return;
props = scene.blenderkit_brush
return props
def get_active_asset():
scene = bpy.context.scene
ui_props = scene.blenderkitUI
if ui_props.asset_type == 'MODEL':
if bpy.context.view_layer.objects.active is not None:
ob = get_active_model()
return ob
if ui_props.asset_type == 'SCENE':
return bpy.context.scene
elif ui_props.asset_type == 'MATERIAL':
if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
return bpy.context.active_object.active_material
elif ui_props.asset_type == 'TEXTURE':
return None
elif ui_props.asset_type == 'BRUSH':
b = get_active_brush()
if b is not None:
return b
return None
def get_upload_props():
scene = bpy.context.scene
ui_props = scene.blenderkitUI
if ui_props.asset_type == 'MODEL':
if bpy.context.view_layer.objects.active is not None:
ob = get_active_model()
return ob.blenderkit
if ui_props.asset_type == 'SCENE':
s = bpy.context.scene
return s.blenderkit
elif ui_props.asset_type == 'MATERIAL':
if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
return bpy.context.active_object.active_material.blenderkit
elif ui_props.asset_type == 'TEXTURE':
return None
elif ui_props.asset_type == 'BRUSH':
b = get_active_brush()
if b is not None:
return b.blenderkit
return None
def previmg_name(index, fullsize=False):
if not fullsize:
def get_active_brush():
context = bpy.context
brush = None
if context.sculpt_object:
brush = context.tool_settings.sculpt.brush
elif context.image_paint_object: # could be just else, but for future possible more types...
brush = context.tool_settings.image_paint.brush
return brush
def load_prefs():
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
# if user_preferences.api_key == '':
fpath = paths.BLENDERKIT_SETTINGS_FILENAME
if os.path.exists(fpath):
with open(fpath, 'r') as s:
prefs = json.load(s)
user_preferences.api_key = prefs.get('API_key', '')
Vilem Duha
committed
user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
user_preferences.api_key_refresh = prefs.get('API_key_refresh', '')
# first check context, so we don't do this on registration or blender startup
if not bpy.app.background: # (hasattr kills blender)
user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
# we test the api key for length, so not a random accidentally typed sequence gets saved.
lk = len(user_preferences.api_key)
if 0 < lk < 25:
# reset the api key in case the user writes some nonsense, e.g. a search string instead of the Key
user_preferences.api_key = ''
props = get_search_props()
props.report = 'Login failed. Please paste a correct API Key.'
prefs = {
'API_key': user_preferences.api_key,
'API_key_refresh': user_preferences.api_key_refresh,
'global_dir': user_preferences.global_dir,
}
try:
fpath = paths.BLENDERKIT_SETTINGS_FILENAME
if not os.path.exists(paths._presets):
os.makedirs(paths._presets)
f = open(fpath, 'w')
with open(fpath, 'w') as s:
json.dump(prefs, s)
except Exception as e:
print(e)
def get_hidden_texture(tpath, bdata_name, force_reload=False):
i = get_hidden_image(tpath, bdata_name, force_reload=force_reload)
bdata_name = f".{bdata_name}"
t = bpy.data.textures.get(bdata_name)
if t is None:
t = bpy.data.textures.new('.test', 'IMAGE')
def get_hidden_image(tpath, bdata_name, force_reload=False):
hidden_name = '.%s' % bdata_name
img = bpy.data.images.get(hidden_name)
if tpath.startswith('//'):
tpath = bpy.path.abspath(tpath)
gap = '\n\n\n'
en = '\n'
if img == None or (img.filepath != tpath):
if tpath.startswith('//'):
tpath = bpy.path.abspath(tpath)
if not os.path.exists(tpath) or os.path.isdir(tpath):
tpath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
if img is None:
img = bpy.data.images.load(tpath)
img.name = hidden_name
else:
if img.filepath != tpath:
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.filepath = tpath
img.reload()
img.colorspace_settings.name = 'sRGB'
elif force_reload:
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.reload()
img.colorspace_settings.name = 'sRGB'
return img
def get_thumbnail(name):
p = paths.get_addon_thumbnail_path(name)
name = '.%s' % name
img = bpy.data.images.get(name)
if img == None:
img = bpy.data.images.load(p)
img.colorspace_settings.name = 'sRGB'
img.name = name
img.name = name
return img
def files_size_to_text(size):
fsmb = size // (1024 * 1024)
fskb = size % 1024
if fsmb == 0:
return f'{fskb}KB'
else:
return f'{fsmb}MB {fskb}KB'
def get_brush_props(context):
brush = get_active_brush()
if brush is not None:
return brush.blenderkit
return None
def p(text, text1='', text2='', text3='', text4='', text5=''):
'''debug printing depending on blender's debug value'''
if bpy.app.debug_value != 0:
print('\n')
# print('-----BKit debug-----\n')
# traceback.print_stack()
print(text, text1, text2, text3, text4, text5)
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# print('---------------------\n')
def copy_asset(fp1, fp2):
'''synchronizes the asset between folders, including it's texture subdirectories'''
if 1:
p('copy asset')
p(fp1, fp2)
if not os.path.exists(fp2):
shutil.copyfile(fp1, fp2)
p('copied')
source_dir = os.path.dirname(fp1)
target_dir = os.path.dirname(fp2)
for subdir in os.scandir(source_dir):
if not subdir.is_dir():
continue
target_subdir = os.path.join(target_dir, subdir.name)
if os.path.exists(target_subdir):
continue
p(subdir, target_subdir)
shutil.copytree(subdir, target_subdir)
p('copied')
# except Exception as e:
# print('BlenderKit failed to copy asset')
# print(fp1, fp2)
# print(e)
def pprint(data, data1=None, data2=None, data3=None, data4=None):
p(json.dumps(data, indent=4, sort_keys=True))
'''get all objects in a tree'''
while len(doobs) > 0:
o = doobs.pop()
doobs.extend(o.children)
obs.append(o)
return obs
def select_hierarchy(ob, state=True):
obs = get_hierarchy(ob)
for ob in obs:
ob.select_set(state)
return obs
def delete_hierarchy(ob):
obs = get_hierarchy(ob)
bpy.ops.object.delete({"selected_objects": obs})
def get_bounds_snappable(obs, use_modifiers=False):
# progress('getting bounds of object(s)')
parent = obs[0]
while parent.parent is not None:
parent = parent.parent
maxx = maxy = maxz = -10000000
minx = miny = minz = 10000000
s = bpy.context.scene
obcount = 0 # calculates the mesh obs. Good for non-mesh objects
matrix_parent = parent.matrix_world
for ob in obs:
# bb=ob.bound_box
mw = ob.matrix_world
subp = ob.parent
# while parent.parent is not None:
# mw =
if ob.type == 'MESH' or ob.type == 'CURVE':
# If to_mesh() works we can use it on curves and any other ob type almost.
# disabled to_mesh for 2.8 by now, not wanting to use dependency graph yet.
depsgraph = bpy.context.evaluated_depsgraph_get()
object_eval = ob.evaluated_get(depsgraph)
if ob.type == 'CURVE':
mesh = object_eval.to_mesh()
else:
mesh = object_eval.data
# to_mesh(context.depsgraph, apply_modifiers=self.applyModifiers, calc_undeformed=False)
if mesh is not None:
for c in mesh.vertices:
coord = c.co
parent_coord = matrix_parent.inverted() @ mw @ Vector(
(coord[0], coord[1], coord[2])) # copy this when it works below.
minx = min(minx, parent_coord.x)
miny = min(miny, parent_coord.y)
minz = min(minz, parent_coord.z)
maxx = max(maxx, parent_coord.x)
maxy = max(maxy, parent_coord.y)
maxz = max(maxz, parent_coord.z)
# bpy.data.meshes.remove(mesh)
if ob.type == 'CURVE':
object_eval.to_mesh_clear()
if obcount == 0:
minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
minx *= parent.scale.x
maxx *= parent.scale.x
miny *= parent.scale.y
maxy *= parent.scale.y
minz *= parent.scale.z
maxz *= parent.scale.z
return minx, miny, minz, maxx, maxy, maxz
def get_bounds_worldspace(obs, use_modifiers=False):
# progress('getting bounds of object(s)')
s = bpy.context.scene
maxx = maxy = maxz = -10000000
minx = miny = minz = 10000000
obcount = 0 # calculates the mesh obs. Good for non-mesh objects
for ob in obs:
# bb=ob.bound_box
mw = ob.matrix_world
if ob.type == 'MESH' or ob.type == 'CURVE':
depsgraph = bpy.context.evaluated_depsgraph_get()
ob_eval = ob.evaluated_get(depsgraph)
mesh = ob_eval.to_mesh()
if mesh is not None:
for c in mesh.vertices:
coord = c.co
world_coord = mw @ Vector((coord[0], coord[1], coord[2]))
minx = min(minx, world_coord.x)
miny = min(miny, world_coord.y)
minz = min(minz, world_coord.z)
maxx = max(maxx, world_coord.x)
maxy = max(maxy, world_coord.y)
maxz = max(maxz, world_coord.z)
if obcount == 0:
minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
return minx, miny, minz, maxx, maxy, maxz
def is_linked_asset(ob):
return ob.get('asset_data') and ob.instance_collection != None
def get_dimensions(obs):
minx, miny, minz, maxx, maxy, maxz = get_bounds_snappable(obs)
bbmin = Vector((minx, miny, minz))
bbmax = Vector((maxx, maxy, maxz))
dim = Vector((maxx - minx, maxy - miny, maxz - minz))
return dim, bbmin, bbmax
def requests_post_thread(url, json, headers):
r = rerequests.post(url, json=json, verify=True, headers=headers)
def get_headers(api_key):
headers = {
"accept": "application/json",
}
if api_key != '':
headers["Authorization"] = "Bearer %s" % api_key
return headers
def scale_2d(v, s, p):
'''scale a 2d vector with a pivot'''
return (p[0] + s[0] * (v[0] - p[0]), p[1] + s[1] * (v[1] - p[1]))
def scale_uvs(ob, scale=1.0, pivot=Vector((.5, .5))):
mesh = ob.data
if len(mesh.uv_layers) > 0:
uv = mesh.uv_layers[mesh.uv_layers.active_index]
# Scale a UV map iterating over its coordinates to a given scale and with a pivot point
for uvindex in range(len(uv.data)):
uv.data[uvindex].uv = scale_2d(uv.data[uvindex].uv, scale, pivot)
# map uv cubic and switch of auto tex space and set it to 1,1,1
def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False, just_scale=False):
s = bpy.context.scene
mat_props = s.blenderkit_mat
if mat_props.automap:
tob = bpy.data.objects[target_object]
# only automap mesh models
if tob.type == 'MESH' and len(tob.data.polygons) > 0:
# check polycount for a rare case where no polys are in editmesh
actob = bpy.context.active_object
bpy.context.view_layer.objects.active = tob
# auto tex space
if tob.data.use_auto_texspace:
tob.data.use_auto_texspace = False
if not just_scale:
tob.data.texspace_size = (1, 1, 1)
if 'automap' not in tob.data.uv_layers:
bpy.ops.mesh.uv_texture_add()
uvl = tob.data.uv_layers[-1]
uvl.name = 'automap'
# TODO limit this to active material
# tob.data.uv_textures['automap'].active = True
scale = tob.scale.copy()
if target_slot is not None:
tob.active_material_index = target_slot
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
# this exception is just for a 2.8 background thunmbnailer crash, can be removed when material slot select works...
if bg_exception:
bpy.ops.mesh.select_all(action='SELECT')
else:
bpy.ops.object.material_slot_select()
scale = (scale.x + scale.y + scale.z) / 3.0
if not just_scale:
bpy.ops.uv.cube_project(
cube_size=scale * 2.0 / (tex_size),
correct_aspect=False) # it's * 2.0 because blender can't tell size of a unit cube :)
bpy.ops.object.editmode_toggle()
tob.data.uv_layers.active = tob.data.uv_layers['automap']
tob.data.uv_layers["automap"].active_render = True
# this by now works only for thumbnail preview, but should be extended to work on arbitrary objects.
# by now, it takes the basic uv map = 1 meter. also, it now doeasn't respect more materials on one object,
# it just scales whole UV.
if just_scale:
scale_uvs(tob, scale=Vector((1 / tex_size, 1 / tex_size)))
def name_update():
props = get_upload_props()
if props.name_old != props.name:
props.name_changed = True
props.name_old = props.name
nname = props.name.strip()
nname = nname.replace('_', ' ')
if nname.isupper():
nname = nname.lower()
nname = nname[0].upper() + nname[1:]
props.name = nname
# here we need to fix the name for blender data = ' or " give problems in path evaluation down the road.
fname = props.name
fname = fname.replace('\'', '')
fname = fname.replace('\"', '')
asset = get_active_asset()
def get_param(asset_data, parameter_name):
if not asset_data.get('parameters'):
# this can appear in older version files.
return None
for p in asset_data['parameters']:
if p.get('parameterType') == parameter_name:
return p['value']
return None
def params_to_dict(params):
params_dict = {}
for p in params:
params_dict[p['parameterType']] = p['value']
return params_dict
def dict_to_params(inputs, parameters=None):
if parameters == None:
parameters = []
for k in inputs.keys():
if type(inputs[k]) == list:
strlist = ""
for idx, s in enumerate(inputs[k]):
strlist += s
if idx < len(inputs[k]) - 1:
strlist += ','
value = "%s" % strlist
elif type(inputs[k]) != bool:
value = inputs[k]
else:
value = str(inputs[k])
parameters.append(
{
"parameterType": k,
"value": value
})
return parameters
def user_logged_in():
a = bpy.context.window_manager.get('bkit profile')
if a is not None:
return True
return False
def profile_is_validator():
a = bpy.context.window_manager.get('bkit profile')
if a is not None and a['user'].get('exmenu'):
return True
def guard_from_crash():
'''Blender tends to crash when trying to run some functions with the addon going through unregistration process.'''
if bpy.context.preferences.addons.get('blenderkit') is None:
return False;
if bpy.context.preferences.addons['blenderkit'].preferences is None:
return False;
def get_largest_area(area_type='VIEW_3D'):
maxsurf = 0
maxa = None
maxw = None
region = None
for a in w.screen.areas:
if a.type == area_type:
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_pointer, active_window_pointer, active_region_pointer
active_window_pointer = maxw.as_pointer()
active_area_pointer = maxa.as_pointer()
active_region_pointer = region.as_pointer()
return maxw, maxa, region
def get_fake_context(context, area_type='VIEW_3D'):
C_dict = {} # context.copy() #context.copy was a source of problems - incompatibility with addons that also define context
C_dict.update(region='WINDOW')
try:
context = context.copy()
Vilém Duha
committed
except Exception as e:
print(e)
print('BlenderKit: context.copy() failed. Can be a colliding addon.')
context = {}
if context.get('area') is None or context.get('area').type != area_type:
w, a, r = get_largest_area(area_type=area_type)
Vilém Duha
committed
if w:
# sometimes there is no area of the requested type. Let's face it, some people use Blender without 3d view.
Vilém Duha
committed
override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
C_dict.update(override)
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
def label_multiline(layout, text='', icon='NONE', width=-1):
''' draw a ui label, but try to split it in multiple lines.'''
if text.strip() == '':
return
lines = text.split('\n')
if width > 0:
threshold = int(width / 5.5)
else:
threshold = 35
maxlines = 8
li = 0
for l in lines:
while len(l) > threshold:
i = l.rfind(' ', 0, threshold)
if i < 1:
i = threshold
l1 = l[:i]
layout.label(text=l1, icon=icon)
icon = 'NONE'
l = l[i:].lstrip()
li += 1
if li > maxlines:
break;
if li > maxlines:
break;
layout.label(text=l, icon=icon)