import bpy, sys, traceback, addon_utils from bpy.types import Object from . import __package__ as base_package # NOTE: This file should not import anything from this add-on! def get_pretty_stack() -> str: """Make a pretty looking string out of the current execution stack, or the exception stack if this is called from a stack which is handling an exception. (Python is cool in that way - We can tell when this function is being called by a stack which originated in a try/except block!) """ ret = "" exc_type, exc_value, tb = sys.exc_info() if exc_value: # If the stack we're currently on is handling an exception, # use the stack of that exception instead of our stack stack = traceback.extract_tb(exc_value.__traceback__) else: stack = traceback.extract_stack() lines = [] after_generator = False for i, frame in enumerate(stack): if 'generator' in frame.filename: after_generator = True if not after_generator: continue if frame.name in ( "log", "add_log", "log_fatal_error", "raise_generation_error", ): break # Shorten the file name; All files are in blender's "scripts" folder, so # that part of the path contains no useful information, just clutter. short_file = frame.filename if 'scripts' in short_file: short_file = frame.filename.split("scripts")[1] if i > 0 and frame.filename == stack[i - 1].filename: short_file = " " * int(len(frame.filename) / 2) lines.append(f"{short_file} -> {frame.name} -> line {frame.lineno}") ret += f" {chr(8629)}\n".join(lines) ret += f":\n {frame.line}\n" if exc_value: ret += f"{exc_type.__name__}: {exc_value}" return ret def get_datablock_type_icon(datablock): """Return the icon string representing a datablock type""" # It's beautiful. # There's no proper way to get the icon of a datablock, so we use the # RNA definition of the id_type property of the DriverTarget class, # which is an enum with a mapping of each datablock type to its icon. # TODO: It would unfortunately be nicer to just make my own mapping. if not hasattr(datablock, "type"): # shape keys... return 'NONE' typ = datablock.type if datablock.type == 'SHADER': typ = 'NODETREE' return bpy.types.DriverTarget.bl_rna.properties['id_type'].enum_items[typ].icon def get_sidebar_size(context): for region in context.area.regions: if region.type == 'UI': return region.width def draw_label_with_linebreak(context, layout, text, alert=False): """Attempt to simulate a proper textbox by only displaying as many characters in a single label as fits in the UI. This only works well on specific UI zoom levels. """ if text == "": return col = layout.column(align=True) col.alert = alert paragraphs = text.split("\n") # Try to determine maximum allowed characters per line, based on pixel width of the area. # Not a great metric, but I couldn't find anything better. max_line_length = get_sidebar_size(context) / 8 for p in paragraphs: lines = [""] for word in p.split(" "): if len(lines[-1]) + len(word) + 1 > max_line_length: lines.append("") lines[-1] += word + " " for line in lines: col.label(text=line) return col def get_object_hierarchy_recursive(obj: Object, all_objects=[]): if obj not in all_objects: all_objects.append(obj) for c in obj.children: get_object_hierarchy_recursive(c, all_objects) return all_objects def check_addon(context, addon_name: str) -> bool: """Same as addon_utils.check() but account for workspace-specific disabling. Return whether an addon is enabled in this context. """ addon_enabled_in_userprefs = addon_utils.check(addon_name)[1] if addon_enabled_in_userprefs and context.workspace.use_filter_by_owner: # Not sure why it's called owner_ids, but it contains a list of enabled addons in this workspace. addon_enabled_in_workspace = addon_name in context.workspace.owner_ids return addon_enabled_in_workspace return addon_enabled_in_userprefs def get_addon_prefs(context=None): if not context: context = bpy.context if base_package.startswith('bl_ext'): # 4.2 return context.preferences.addons[base_package].preferences else: return context.preferences.addons[base_package.split(".")[0]].preferences