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 #####
from blenderkit import paths
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():
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 not 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
return parents
def get_search_props():
scene = bpy.context.scene
if scene is None:
return;
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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:
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
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:
return '.bkit_preview_' + str(index).zfill(2)
else:
return '.bkit_preview_full_' + str(index).zfill(2)
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_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 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()
Vilém Duha
committed
img.colorspace_settings.name = 'Linear'
elif force_reload:
if img.packed_file is not None:
img.unpack(method='USE_ORIGINAL')
img.reload()
Vilém Duha
committed
img.colorspace_settings.name = 'Linear'
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 = 'Linear'
img.name = name
img.name = name
return img
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(text, text1, text2, text3, text4, text5)
p(json.dumps(data, indent=4, sort_keys=True))
'''get all objects in a tree'''
obs = []
doobs = [ob]
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):
from blenderkit import bg_blender as bg
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':
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()
asset.name = fname