Cleanup: simplify file name incrementing logic
[blender-addons.git] / blenderkit / utils.py
blob26ace999b54541e644a93dff66e4d36cd09f8569
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 from blenderkit import paths, rerequests, image_utils
22 import bpy
23 from mathutils import Vector
24 import json
25 import os
26 import sys
27 import shutil
28 import logging
29 import traceback
30 import inspect
32 bk_logger = logging.getLogger('blenderkit')
34 ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000
35 BELOW_NORMAL_PRIORITY_CLASS = 0x00004000
36 HIGH_PRIORITY_CLASS = 0x00000080
37 IDLE_PRIORITY_CLASS = 0x00000040
38 NORMAL_PRIORITY_CLASS = 0x00000020
39 REALTIME_PRIORITY_CLASS = 0x00000100
42 def experimental_enabled():
43 preferences = bpy.context.preferences.addons['blenderkit'].preferences
44 return preferences.experimental_features
47 def get_process_flags():
48 flags = BELOW_NORMAL_PRIORITY_CLASS
49 if sys.platform != 'win32': # TODO test this on windows
50 flags = 0
51 return flags
54 def activate(ob):
55 bpy.ops.object.select_all(action='DESELECT')
56 ob.select_set(True)
57 bpy.context.view_layer.objects.active = ob
60 def selection_get():
61 aob = bpy.context.view_layer.objects.active
62 selobs = bpy.context.view_layer.objects.selected[:]
63 return (aob, selobs)
66 def selection_set(sel):
67 bpy.ops.object.select_all(action='DESELECT')
68 bpy.context.view_layer.objects.active = sel[0]
69 for ob in sel[1]:
70 ob.select_set(True)
73 def get_active_model():
74 if bpy.context.view_layer.objects.active is not None:
75 ob = bpy.context.view_layer.objects.active
76 while ob.parent is not None:
77 ob = ob.parent
78 return ob
79 return None
82 def get_active_HDR():
83 scene = bpy.context.scene
84 ui_props = scene.blenderkitUI
85 image = ui_props.hdr_upload_image
86 return image
89 def get_selected_models():
90 '''
91 Detect all hierarchies that contain asset data from selection. Only parents that have actual ['asset data'] get returned
92 Returns
93 list of objects containing asset data.
95 '''
96 obs = bpy.context.selected_objects[:]
97 done = {}
98 parents = []
99 for ob in obs:
100 if ob not in done:
101 while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
102 done[ob] = True
103 ob = ob.parent
105 if ob not in parents and ob not in done:
106 if ob.blenderkit.name != '' or ob.instance_collection is not None:
107 parents.append(ob)
108 done[ob] = True
110 # if no blenderkit - like objects were found, use the original selection.
111 if len(parents) == 0:
112 parents = obs
113 return parents
116 def get_selected_replace_adepts():
118 Detect all hierarchies that contain either asset data from selection, or selected objects themselves.
119 Returns
120 list of objects for replacement.
123 obs = bpy.context.selected_objects[:]
124 done = {}
125 parents = []
126 for selected_ob in obs:
127 ob = selected_ob
128 if ob not in done:
129 while ob.parent is not None and ob not in done and ob.blenderkit.asset_base_id == '' and ob.instance_collection is None:
130 done[ob] = True
131 # print('step,',ob.name)
132 ob = ob.parent
134 # print('fin', ob.name)
135 if ob not in parents and ob not in done:
136 if ob.blenderkit.name != '' or ob.instance_collection is not None:
137 parents.append(ob)
139 done[ob] = True
140 # print(parents)
141 # if no blenderkit - like objects were found, use the original selection.
142 if len(parents) == 0:
143 parents = obs
144 pprint('replace adepts')
145 pprint(str(parents))
146 return parents
149 def get_search_props():
150 scene = bpy.context.scene
151 if scene is None:
152 return;
153 uiprops = scene.blenderkitUI
154 props = None
155 if uiprops.asset_type == 'MODEL':
156 if not hasattr(scene, 'blenderkit_models'):
157 return;
158 props = scene.blenderkit_models
159 if uiprops.asset_type == 'SCENE':
160 if not hasattr(scene, 'blenderkit_scene'):
161 return;
162 props = scene.blenderkit_scene
163 if uiprops.asset_type == 'HDR':
164 if not hasattr(scene, 'blenderkit_HDR'):
165 return;
166 props = scene.blenderkit_HDR
167 if uiprops.asset_type == 'MATERIAL':
168 if not hasattr(scene, 'blenderkit_mat'):
169 return;
170 props = scene.blenderkit_mat
172 if uiprops.asset_type == 'TEXTURE':
173 if not hasattr(scene, 'blenderkit_tex'):
174 return;
175 # props = scene.blenderkit_tex
177 if uiprops.asset_type == 'BRUSH':
178 if not hasattr(scene, 'blenderkit_brush'):
179 return;
180 props = scene.blenderkit_brush
181 return props
184 def get_active_asset():
185 scene = bpy.context.scene
186 ui_props = scene.blenderkitUI
187 if ui_props.asset_type == 'MODEL':
188 if bpy.context.view_layer.objects.active is not None:
189 ob = get_active_model()
190 return ob
191 if ui_props.asset_type == 'SCENE':
192 return bpy.context.scene
193 if ui_props.asset_type == 'HDR':
194 return get_active_HDR()
195 elif ui_props.asset_type == 'MATERIAL':
196 if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
197 return bpy.context.active_object.active_material
198 elif ui_props.asset_type == 'TEXTURE':
199 return None
200 elif ui_props.asset_type == 'BRUSH':
201 b = get_active_brush()
202 if b is not None:
203 return b
204 return None
207 def get_upload_props():
208 scene = bpy.context.scene
209 ui_props = scene.blenderkitUI
210 if ui_props.asset_type == 'MODEL':
211 if bpy.context.view_layer.objects.active is not None:
212 ob = get_active_model()
213 return ob.blenderkit
214 if ui_props.asset_type == 'SCENE':
215 s = bpy.context.scene
216 return s.blenderkit
217 if ui_props.asset_type == 'HDR':
219 hdr = ui_props.hdr_upload_image # bpy.data.images.get(ui_props.hdr_upload_image)
220 if not hdr:
221 return None
222 return hdr.blenderkit
223 elif ui_props.asset_type == 'MATERIAL':
224 if bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None:
225 return bpy.context.active_object.active_material.blenderkit
226 elif ui_props.asset_type == 'TEXTURE':
227 return None
228 elif ui_props.asset_type == 'BRUSH':
229 b = get_active_brush()
230 if b is not None:
231 return b.blenderkit
232 return None
235 def previmg_name(index, fullsize=False):
236 if not fullsize:
237 return '.bkit_preview_' + str(index).zfill(3)
238 else:
239 return '.bkit_preview_full_' + str(index).zfill(3)
242 def get_active_brush():
243 context = bpy.context
244 brush = None
245 if context.sculpt_object:
246 brush = context.tool_settings.sculpt.brush
247 elif context.image_paint_object: # could be just else, but for future possible more types...
248 brush = context.tool_settings.image_paint.brush
249 return brush
252 def load_prefs():
253 user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
254 # if user_preferences.api_key == '':
255 fpath = paths.BLENDERKIT_SETTINGS_FILENAME
256 if os.path.exists(fpath):
257 try:
258 with open(fpath, 'r', encoding='utf-8') as s:
259 prefs = json.load(s)
260 user_preferences.api_key = prefs.get('API_key', '')
261 user_preferences.global_dir = prefs.get('global_dir', paths.default_global_dict())
262 user_preferences.api_key_refresh = prefs.get('API_key_refresh', '')
263 except Exception as e:
264 print('failed to read addon preferences.')
265 print(e)
266 os.remove(fpath)
269 def save_prefs(self, context):
270 # first check context, so we don't do this on registration or blender startup
271 if not bpy.app.background: # (hasattr kills blender)
272 user_preferences = bpy.context.preferences.addons['blenderkit'].preferences
273 # we test the api key for length, so not a random accidentally typed sequence gets saved.
274 lk = len(user_preferences.api_key)
275 if 0 < lk < 25:
276 # reset the api key in case the user writes some nonsense, e.g. a search string instead of the Key
277 user_preferences.api_key = ''
278 props = get_search_props()
279 props.report = 'Login failed. Please paste a correct API Key.'
281 prefs = {
282 'API_key': user_preferences.api_key,
283 'API_key_refresh': user_preferences.api_key_refresh,
284 'global_dir': user_preferences.global_dir,
286 try:
287 fpath = paths.BLENDERKIT_SETTINGS_FILENAME
288 if not os.path.exists(paths._presets):
289 os.makedirs(paths._presets)
290 with open(fpath, 'w', encoding='utf-8') as s:
291 json.dump(prefs, s, ensure_ascii=False, indent=4)
292 except Exception as e:
293 print(e)
296 def uploadable_asset_poll():
297 '''returns true if active asset type can be uploaded'''
298 ui_props = bpy.context.scene.blenderkitUI
299 if ui_props.asset_type == 'MODEL':
300 return bpy.context.view_layer.objects.active is not None
301 if ui_props.asset_type == 'MATERIAL':
302 return bpy.context.view_layer.objects.active is not None and bpy.context.active_object.active_material is not None
303 if ui_props.asset_type == 'HDR':
304 return ui_props.hdr_upload_image is not None
305 return True
308 def get_hidden_texture(img, force_reload=False):
309 # i = get_hidden_image(tpath, bdata_name, force_reload=force_reload)
310 # bdata_name = f".{bdata_name}"
311 t = bpy.data.textures.get(img.name)
312 if t is None:
313 t = bpy.data.textures.new(img.name, 'IMAGE')
314 if t.image != img:
315 t.image = img
316 return t
319 def get_hidden_image(tpath, bdata_name, force_reload=False, colorspace='sRGB'):
320 if bdata_name[0] == '.':
321 hidden_name = bdata_name
322 else:
323 hidden_name = '.%s' % bdata_name
324 img = bpy.data.images.get(hidden_name)
326 if tpath.startswith('//'):
327 tpath = bpy.path.abspath(tpath)
329 if img == None or (img.filepath != tpath):
330 if tpath.startswith('//'):
331 tpath = bpy.path.abspath(tpath)
332 if not os.path.exists(tpath) or os.path.isdir(tpath):
333 tpath = paths.get_addon_thumbnail_path('thumbnail_notready.jpg')
335 if img is None:
336 img = bpy.data.images.load(tpath)
337 img.name = hidden_name
338 else:
339 if img.filepath != tpath:
340 if img.packed_file is not None:
341 img.unpack(method='USE_ORIGINAL')
343 img.filepath = tpath
344 img.reload()
345 image_utils.set_colorspace(img, colorspace)
347 elif force_reload:
348 if img.packed_file is not None:
349 img.unpack(method='USE_ORIGINAL')
350 img.reload()
351 image_utils.set_colorspace(img, colorspace)
352 return img
355 def get_thumbnail(name):
356 p = paths.get_addon_thumbnail_path(name)
357 name = '.%s' % name
358 img = bpy.data.images.get(name)
359 if img == None:
360 img = bpy.data.images.load(p)
361 image_utils.set_colorspace(img, 'sRGB')
362 img.name = name
363 img.name = name
365 return img
368 def files_size_to_text(size):
369 fsmb = size // (1024 * 1024)
370 fskb = size % 1024
371 if fsmb == 0:
372 return f'{fskb}KB'
373 else:
374 return f'{fsmb}MB {fskb}KB'
377 def get_brush_props(context):
378 brush = get_active_brush()
379 if brush is not None:
380 return brush.blenderkit
381 return None
384 def p(text, text1='', text2='', text3='', text4='', text5='', level='DEBUG'):
385 '''debug printing depending on blender's debug value'''
387 if 1: # bpy.app.debug_value != 0:
388 # print('-----BKit debug-----\n')
389 # traceback.print_stack()
390 texts = [text1, text2, text3, text4, text5]
391 text = str(text)
392 for t in texts:
393 if t != '':
394 text += ' ' + str(t)
396 bk_logger.debug(text)
397 # print('---------------------\n')
400 def copy_asset(fp1, fp2):
401 '''synchronizes the asset between folders, including it's texture subdirectories'''
402 if 1:
403 bk_logger.debug('copy asset')
404 bk_logger.debug(fp1 + ' ' + fp2)
405 if not os.path.exists(fp2):
406 shutil.copyfile(fp1, fp2)
407 bk_logger.debug('copied')
408 source_dir = os.path.dirname(fp1)
409 target_dir = os.path.dirname(fp2)
410 for subdir in os.scandir(source_dir):
411 if not subdir.is_dir():
412 continue
413 target_subdir = os.path.join(target_dir, subdir.name)
414 if os.path.exists(target_subdir):
415 continue
416 bk_logger.debug(str(subdir) + ' ' + str(target_subdir))
417 shutil.copytree(subdir, target_subdir)
418 bk_logger.debug('copied')
420 # except Exception as e:
421 # print('BlenderKit failed to copy asset')
422 # print(fp1, fp2)
423 # print(e)
426 def pprint(data, data1=None, data2=None, data3=None, data4=None):
427 '''pretty print jsons'''
428 p(json.dumps(data, indent=4, sort_keys=True))
431 def get_hierarchy(ob):
432 '''get all objects in a tree'''
433 obs = []
434 doobs = [ob]
435 # pprint('get hierarchy')
436 pprint(ob.name)
437 while len(doobs) > 0:
438 o = doobs.pop()
439 doobs.extend(o.children)
440 obs.append(o)
441 return obs
444 def select_hierarchy(ob, state=True):
445 obs = get_hierarchy(ob)
446 for ob in obs:
447 ob.select_set(state)
448 return obs
451 def delete_hierarchy(ob):
452 obs = get_hierarchy(ob)
453 bpy.ops.object.delete({"selected_objects": obs})
456 def get_bounds_snappable(obs, use_modifiers=False):
457 # progress('getting bounds of object(s)')
458 parent = obs[0]
459 while parent.parent is not None:
460 parent = parent.parent
461 maxx = maxy = maxz = -10000000
462 minx = miny = minz = 10000000
464 s = bpy.context.scene
466 obcount = 0 # calculates the mesh obs. Good for non-mesh objects
467 matrix_parent = parent.matrix_world
468 for ob in obs:
469 # bb=ob.bound_box
470 mw = ob.matrix_world
471 subp = ob.parent
472 # while parent.parent is not None:
473 # mw =
475 if ob.type == 'MESH' or ob.type == 'CURVE':
476 # If to_mesh() works we can use it on curves and any other ob type almost.
477 # disabled to_mesh for 2.8 by now, not wanting to use dependency graph yet.
478 depsgraph = bpy.context.evaluated_depsgraph_get()
480 object_eval = ob.evaluated_get(depsgraph)
481 if ob.type == 'CURVE':
482 mesh = object_eval.to_mesh()
483 else:
484 mesh = object_eval.data
486 # to_mesh(context.depsgraph, apply_modifiers=self.applyModifiers, calc_undeformed=False)
487 obcount += 1
488 if mesh is not None:
489 for c in mesh.vertices:
490 coord = c.co
491 parent_coord = matrix_parent.inverted() @ mw @ Vector(
492 (coord[0], coord[1], coord[2])) # copy this when it works below.
493 minx = min(minx, parent_coord.x)
494 miny = min(miny, parent_coord.y)
495 minz = min(minz, parent_coord.z)
496 maxx = max(maxx, parent_coord.x)
497 maxy = max(maxy, parent_coord.y)
498 maxz = max(maxz, parent_coord.z)
499 # bpy.data.meshes.remove(mesh)
500 if ob.type == 'CURVE':
501 object_eval.to_mesh_clear()
503 if obcount == 0:
504 minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
506 minx *= parent.scale.x
507 maxx *= parent.scale.x
508 miny *= parent.scale.y
509 maxy *= parent.scale.y
510 minz *= parent.scale.z
511 maxz *= parent.scale.z
513 return minx, miny, minz, maxx, maxy, maxz
516 def get_bounds_worldspace(obs, use_modifiers=False):
517 # progress('getting bounds of object(s)')
518 s = bpy.context.scene
519 maxx = maxy = maxz = -10000000
520 minx = miny = minz = 10000000
521 obcount = 0 # calculates the mesh obs. Good for non-mesh objects
522 for ob in obs:
523 # bb=ob.bound_box
524 mw = ob.matrix_world
525 if ob.type == 'MESH' or ob.type == 'CURVE':
526 depsgraph = bpy.context.evaluated_depsgraph_get()
527 ob_eval = ob.evaluated_get(depsgraph)
528 mesh = ob_eval.to_mesh()
529 obcount += 1
530 if mesh is not None:
531 for c in mesh.vertices:
532 coord = c.co
533 world_coord = mw @ Vector((coord[0], coord[1], coord[2]))
534 minx = min(minx, world_coord.x)
535 miny = min(miny, world_coord.y)
536 minz = min(minz, world_coord.z)
537 maxx = max(maxx, world_coord.x)
538 maxy = max(maxy, world_coord.y)
539 maxz = max(maxz, world_coord.z)
540 ob_eval.to_mesh_clear()
542 if obcount == 0:
543 minx, miny, minz, maxx, maxy, maxz = 0, 0, 0, 0, 0, 0
544 return minx, miny, minz, maxx, maxy, maxz
547 def is_linked_asset(ob):
548 return ob.get('asset_data') and ob.instance_collection != None
551 def get_dimensions(obs):
552 minx, miny, minz, maxx, maxy, maxz = get_bounds_snappable(obs)
553 bbmin = Vector((minx, miny, minz))
554 bbmax = Vector((maxx, maxy, maxz))
555 dim = Vector((maxx - minx, maxy - miny, maxz - minz))
556 return dim, bbmin, bbmax
559 def requests_post_thread(url, json, headers):
560 r = rerequests.post(url, json=json, verify=True, headers=headers)
563 def get_headers(api_key):
564 headers = {
565 "accept": "application/json",
567 if api_key != '':
568 headers["Authorization"] = "Bearer %s" % api_key
569 return headers
572 def scale_2d(v, s, p):
573 '''scale a 2d vector with a pivot'''
574 return (p[0] + s[0] * (v[0] - p[0]), p[1] + s[1] * (v[1] - p[1]))
577 def scale_uvs(ob, scale=1.0, pivot=Vector((.5, .5))):
578 mesh = ob.data
579 if len(mesh.uv_layers) > 0:
580 uv = mesh.uv_layers[mesh.uv_layers.active_index]
582 # Scale a UV map iterating over its coordinates to a given scale and with a pivot point
583 for uvindex in range(len(uv.data)):
584 uv.data[uvindex].uv = scale_2d(uv.data[uvindex].uv, scale, pivot)
587 # map uv cubic and switch of auto tex space and set it to 1,1,1
588 def automap(target_object=None, target_slot=None, tex_size=1, bg_exception=False, just_scale=False):
589 s = bpy.context.scene
590 mat_props = s.blenderkit_mat
591 if mat_props.automap:
592 tob = bpy.data.objects[target_object]
593 # only automap mesh models
594 if tob.type == 'MESH' and len(tob.data.polygons) > 0:
595 # check polycount for a rare case where no polys are in editmesh
596 actob = bpy.context.active_object
597 bpy.context.view_layer.objects.active = tob
599 # auto tex space
600 if tob.data.use_auto_texspace:
601 tob.data.use_auto_texspace = False
603 if not just_scale:
604 tob.data.texspace_size = (1, 1, 1)
606 if 'automap' not in tob.data.uv_layers:
607 bpy.ops.mesh.uv_texture_add()
608 uvl = tob.data.uv_layers[-1]
609 uvl.name = 'automap'
611 # TODO limit this to active material
612 # tob.data.uv_textures['automap'].active = True
614 scale = tob.scale.copy()
616 if target_slot is not None:
617 tob.active_material_index = target_slot
618 bpy.ops.object.mode_set(mode='EDIT')
619 bpy.ops.mesh.select_all(action='DESELECT')
621 # this exception is just for a 2.8 background thunmbnailer crash, can be removed when material slot select works...
622 if bg_exception:
623 bpy.ops.mesh.select_all(action='SELECT')
624 else:
625 bpy.ops.object.material_slot_select()
627 scale = (scale.x + scale.y + scale.z) / 3.0
628 if not just_scale:
629 bpy.ops.uv.cube_project(
630 cube_size=scale * 2.0 / (tex_size),
631 correct_aspect=False) # it's * 2.0 because blender can't tell size of a unit cube :)
633 bpy.ops.object.editmode_toggle()
634 tob.data.uv_layers.active = tob.data.uv_layers['automap']
635 tob.data.uv_layers["automap"].active_render = True
636 # this by now works only for thumbnail preview, but should be extended to work on arbitrary objects.
637 # by now, it takes the basic uv map = 1 meter. also, it now doeasn't respect more materials on one object,
638 # it just scales whole UV.
639 if just_scale:
640 scale_uvs(tob, scale=Vector((1 / tex_size, 1 / tex_size)))
641 bpy.context.view_layer.objects.active = actob
644 def name_update(props):
646 Update asset name function, gets run also before upload. Makes sure name doesn't change in case of reuploads,
647 and only displayName gets written to server.
649 scene = bpy.context.scene
650 ui_props = scene.blenderkitUI
652 # props = get_upload_props()
653 if props.name_old != props.name:
654 props.name_changed = True
655 props.name_old = props.name
656 nname = props.name.strip()
657 nname = nname.replace('_', ' ')
659 if nname.isupper():
660 nname = nname.lower()
661 nname = nname[0].upper() + nname[1:]
662 props.name = nname
663 # here we need to fix the name for blender data = ' or " give problems in path evaluation down the road.
664 fname = props.name
665 fname = fname.replace('\'', '')
666 fname = fname.replace('\"', '')
667 asset = get_active_asset()
668 if ui_props.asset_type != 'HDR':
669 # Here we actually rename assets datablocks, but don't do that with HDR's and possibly with others
670 asset.name = fname
673 def get_param(asset_data, parameter_name):
674 if not asset_data.get('parameters'):
675 # this can appear in older version files.
676 return None
678 for p in asset_data['parameters']:
679 if p.get('parameterType') == parameter_name:
680 return p['value']
681 return None
684 def params_to_dict(params):
685 params_dict = {}
686 for p in params:
687 params_dict[p['parameterType']] = p['value']
688 return params_dict
691 def dict_to_params(inputs, parameters=None):
692 if parameters == None:
693 parameters = []
694 for k in inputs.keys():
695 if type(inputs[k]) == list:
696 strlist = ""
697 for idx, s in enumerate(inputs[k]):
698 strlist += s
699 if idx < len(inputs[k]) - 1:
700 strlist += ','
702 value = "%s" % strlist
703 elif type(inputs[k]) != bool:
704 value = inputs[k]
705 else:
706 value = str(inputs[k])
707 parameters.append(
709 "parameterType": k,
710 "value": value
712 return parameters
715 def update_tags(self, context):
716 props = self
718 commasep = props.tags.split(',')
719 ntags = []
720 for tag in commasep:
721 if len(tag) > 19:
722 short_tags = tag.split(' ')
723 for short_tag in short_tags:
724 if len(short_tag) > 19:
725 short_tag = short_tag[:18]
726 ntags.append(short_tag)
727 else:
728 ntags.append(tag)
729 if len(ntags) == 1:
730 ntags = ntags[0].split(' ')
731 ns = ''
732 for t in ntags:
733 if t != '':
734 ns += t + ','
735 ns = ns[:-1]
736 if props.tags != ns:
737 props.tags = ns
740 def user_logged_in():
741 a = bpy.context.window_manager.get('bkit profile')
742 if a is not None:
743 return True
744 return False
747 def profile_is_validator():
748 a = bpy.context.window_manager.get('bkit profile')
749 if a is not None and a['user'].get('exmenu'):
750 return True
751 return False
754 def guard_from_crash():
755 '''Blender tends to crash when trying to run some functions with the addon going through unregistration process.'''
756 if bpy.context.preferences.addons.get('blenderkit') is None:
757 return False;
758 if bpy.context.preferences.addons['blenderkit'].preferences is None:
759 return False;
760 return True
763 def get_largest_area(area_type='VIEW_3D'):
764 maxsurf = 0
765 maxa = None
766 maxw = None
767 region = None
768 for w in bpy.data.window_managers[0].windows:
769 for a in w.screen.areas:
770 if a.type == area_type:
771 asurf = a.width * a.height
772 if asurf > maxsurf:
773 maxa = a
774 maxw = w
775 maxsurf = asurf
777 for r in a.regions:
778 if r.type == 'WINDOW':
779 region = r
780 global active_area_pointer, active_window_pointer, active_region_pointer
781 active_window_pointer = maxw.as_pointer()
782 active_area_pointer = maxa.as_pointer()
783 active_region_pointer = region.as_pointer()
784 return maxw, maxa, region
787 def get_fake_context(context, area_type='VIEW_3D'):
788 C_dict = {} # context.copy() #context.copy was a source of problems - incompatibility with addons that also define context
789 C_dict.update(region='WINDOW')
791 # try:
792 # context = context.copy()
793 # # print('bk context copied successfully')
794 # except Exception as e:
795 # print(e)
796 # print('BlenderKit: context.copy() failed. Can be a colliding addon.')
797 context = {}
799 if context.get('area') is None or context.get('area').type != area_type:
800 w, a, r = get_largest_area(area_type=area_type)
801 if w:
802 # sometimes there is no area of the requested type. Let's face it, some people use Blender without 3d view.
803 override = {'window': w, 'screen': w.screen, 'area': a, 'region': r}
804 C_dict.update(override)
805 # print(w,a,r)
806 return C_dict
809 def label_multiline(layout, text='', icon='NONE', width=-1):
810 ''' draw a ui label, but try to split it in multiple lines.'''
811 if text.strip() == '':
812 return
813 lines = text.split('\n')
814 if width > 0:
815 threshold = int(width / 5.5)
816 else:
817 threshold = 35
818 maxlines = 8
819 li = 0
820 for l in lines:
821 while len(l) > threshold:
822 i = l.rfind(' ', 0, threshold)
823 if i < 1:
824 i = threshold
825 l1 = l[:i]
826 layout.label(text=l1, icon=icon)
827 icon = 'NONE'
828 l = l[i:].lstrip()
829 li += 1
830 if li > maxlines:
831 break;
832 if li > maxlines:
833 break;
834 layout.label(text=l, icon=icon)
835 icon = 'NONE'
838 def trace():
839 traceback.print_stack()