diff options
Diffstat (limited to 'release/scripts/modules')
17 files changed, 413 insertions, 151 deletions
diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py index 8c86f31022c..123b3cb953c 100644 --- a/release/scripts/modules/addon_utils.py +++ b/release/scripts/modules/addon_utils.py @@ -74,40 +74,43 @@ def modules_refresh(module_cache=addons_fake_modules): print("fake_module", mod_path, mod_name) import ast ModuleType = type(ast) - file_mod = open(mod_path, "r", encoding='UTF-8') - if speedy: - lines = [] - line_iter = iter(file_mod) - l = "" - while not l.startswith("bl_info"): - try: - l = line_iter.readline() - except UnicodeDecodeError as e: - if not error_encoding: - error_encoding = True - print("Error reading file as UTF-8:", mod_path, e) - file_mod.close() - return None - - if len(l) == 0: - break - while l.rstrip(): - lines.append(l) - try: - l = line_iter.readline() - except UnicodeDecodeError as e: - if not error_encoding: - error_encoding = True - print("Error reading file as UTF-8:", mod_path, e) - file_mod.close() - return None - - data = "".join(lines) + try: + file_mod = open(mod_path, "r", encoding='UTF-8') + except OSError as e: + print("Error opening file %r: %s" % (mod_path, e)) + return None - else: - data = file_mod.read() + with file_mod: + if speedy: + lines = [] + line_iter = iter(file_mod) + l = "" + while not l.startswith("bl_info"): + try: + l = line_iter.readline() + except UnicodeDecodeError as e: + if not error_encoding: + error_encoding = True + print("Error reading file as UTF-8:", mod_path, e) + return None + + if len(l) == 0: + break + while l.rstrip(): + lines.append(l) + try: + l = line_iter.readline() + except UnicodeDecodeError as e: + if not error_encoding: + error_encoding = True + print("Error reading file as UTF-8:", mod_path, e) + return None + + data = "".join(lines) - file_mod.close() + else: + data = file_mod.read() + del file_mod try: ast_data = ast.parse(data, filename=mod_path) @@ -216,7 +219,8 @@ def check(module_name): loaded_default = module_name in _user_preferences.addons mod = sys.modules.get(module_name) - loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis) + loaded_state = ((mod is not None) and + getattr(mod, "__addon_enabled__", Ellipsis)) if loaded_state is Ellipsis: print("Warning: addon-module %r found module " @@ -274,6 +278,20 @@ def enable(module_name, default_set=False, persistent=False, handle_error=None): mod = sys.modules.get(module_name) # chances of the file _not_ existing are low, but it could be removed if mod and os.path.exists(mod.__file__): + + if getattr(mod, "__addon_enabled__", False): + # This is an unlikely situation, + # re-register if the module is enabled. + # Note: the UI doesn't allow this to happen, + # in most cases the caller should 'check()' first. + try: + mod.unregister() + except: + print("Exception in module unregister(): %r" % + getattr(mod, "__file__", module_name)) + handle_error() + return None + mod.__addon_enabled__ = False mtime_orig = getattr(mod, "__time__", 0) mtime_new = os.path.getmtime(mod.__file__) @@ -342,7 +360,7 @@ def enable(module_name, default_set=False, persistent=False, handle_error=None): return mod -def disable(module_name, default_set=True, handle_error=None): +def disable(module_name, default_set=False, handle_error=None): """ Disables an addon by name. @@ -416,19 +434,21 @@ def reset_all(reload_scripts=False): disable(mod_name) -def module_bl_info(mod, info_basis={"name": "", - "author": "", - "version": (), - "blender": (), - "location": "", - "description": "", - "wiki_url": "", - "support": 'COMMUNITY', - "category": "", - "warning": "", - "show_expanded": False, - } - ): +def module_bl_info(mod, info_basis=None): + if info_basis is None: + info_basis = { + "name": "", + "author": "", + "version": (), + "blender": (), + "location": "", + "description": "", + "wiki_url": "", + "support": 'COMMUNITY', + "category": "", + "warning": "", + "show_expanded": False, + } addon_info = getattr(mod, "bl_info", {}) diff --git a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py index c3b2ae9908b..43a09a1acbd 100644 --- a/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py +++ b/release/scripts/modules/bl_i18n_utils/bl_extract_messages.py @@ -64,7 +64,7 @@ def _gen_check_ctxt(settings): def _diff_check_ctxt(check_ctxt, minus_check_ctxt): - """Returns check_ctxt - minus_check_ctxt""" + """Removes minus_check_ctxt from check_ctxt""" for key in check_ctxt: if isinstance(check_ctxt[key], set): for warning in minus_check_ctxt[key]: @@ -576,8 +576,9 @@ def dump_py_messages_from_files(msgs, reports, files, settings): #print(func_translate_args) # Break recursive nodes look up on some kind of nodes. - # E.g. we don’t want to get strings inside subscripts (blah["foo"])! - stopper_nodes = {ast.Subscript} + # E.g. we don't want to get strings inside subscripts (blah["foo"])! + # we don't want to get strings from comparisons (foo.type == 'BAR'). + stopper_nodes = {ast.Subscript, ast.Compare} # Consider strings separate: ("a" if test else "b") separate_nodes = {ast.IfExp} @@ -897,7 +898,7 @@ def dump_addon_messages(module_name, do_checks, settings): del msgs[key] if check_ctxt: - check_ctxt = _diff_check_ctxt(check_ctxt, minus_check_ctxt) + _diff_check_ctxt(check_ctxt, minus_check_ctxt) # and we are done with those! del minus_pot @@ -924,18 +925,18 @@ def main(): return import sys - back_argv = sys.argv + import argparse + # Get rid of Blender args! - sys.argv = sys.argv[sys.argv.index("--") + 1:] + argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [] - import argparse parser = argparse.ArgumentParser(description="Process UI messages from inside Blender.") parser.add_argument('-c', '--no_checks', default=True, action="store_false", help="No checks over UI messages.") parser.add_argument('-m', '--no_messages', default=True, action="store_false", help="No export of UI messages.") parser.add_argument('-o', '--output', default=None, help="Output POT file path.") parser.add_argument('-s', '--settings', default=None, help="Override (some) default settings. Either a JSon file name, or a JSon string.") - args = parser.parse_args() + args = parser.parse_args(argv) settings = settings_i18n.I18nSettings() settings.from_json(args.settings) @@ -945,8 +946,6 @@ def main(): dump_messages(do_messages=args.no_messages, do_checks=args.no_checks, settings=settings) - sys.argv = back_argv - if __name__ == "__main__": print("\n\n *** Running {} *** \n".format(__file__)) diff --git a/release/scripts/modules/bl_i18n_utils/settings.py b/release/scripts/modules/bl_i18n_utils/settings.py index 920a56a628b..1c960a217de 100644 --- a/release/scripts/modules/bl_i18n_utils/settings.py +++ b/release/scripts/modules/bl_i18n_utils/settings.py @@ -88,6 +88,7 @@ LANGUAGES = ( (38, "Uzbek (Oʻzbek)", "uz_UZ"), (39, "Uzbek Cyrillic (Ўзбек)", "uz_UZ@cyrillic"), (40, "Hindi (मानक हिन्दी)", "hi_IN"), + (41, "Vietnamese (tiếng Việt)", "vi_VN"), ) # Default context, in py! @@ -324,6 +325,7 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "y", # Sub-strings. "available with", + "brown fox", "can't save image while rendering", "expected a timeline/animation area to be active", "expected a view3d region", @@ -332,6 +334,8 @@ WARN_MSGID_NOT_CAPITALIZED_ALLOWED = { "image file not found", "image path can't be written to", "in memory to enable editing!", + "jumps over", + "the lazy dog", "unable to load movie clip", "unable to load text", "unable to open the file", @@ -412,6 +416,15 @@ REL_TRUNK_PO_DIR = os.path.join(REL_TRUNK_DIR, "po") # The /trunk/mo path (relative to I18N_DIR). REL_TRUNK_MO_DIR = os.path.join(REL_TRUNK_DIR, "locale") + +# The path to the *git* translation repository (relative to SOURCE_DIR). +REL_GIT_I18N_DIR = os.path.join("release/datafiles/locale") + + +# The /po path of the *git* translation repository (relative to REL_GIT_I18N_DIR). +REL_GIT_I18N_PO_DIR = os.path.join("po") + + # The Blender source path to check for i18n macros (relative to SOURCE_DIR). REL_POTFILES_SOURCE_DIR = os.path.join("source") @@ -493,14 +506,6 @@ def _gen_get_set_path(ref, name): return _get, _set -def _gen_get_set_paths(ref, name): - def _get(self): - return [_do_get(getattr(self, ref), p) for p in getattr(self, name)] - def _set(self, value): - setattr(self, name, [_do_set(getattr(self, ref), p) for p in value]) - return _get, _set - - class I18nSettings: """ Class allowing persistence of our settings! @@ -552,6 +557,8 @@ class I18nSettings: TRUNK_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_DIR"))) TRUNK_PO_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_PO_DIR"))) TRUNK_MO_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_MO_DIR"))) + GIT_I18N_ROOT = property(*(_gen_get_set_path("SOURCE_DIR", "REL_GIT_I18N_DIR"))) + GIT_I18N_PO_DIR = property(*(_gen_get_set_path("GIT_I18N_ROOT", "REL_GIT_I18N_PO_DIR"))) POTFILES_SOURCE_DIR = property(*(_gen_get_set_path("SOURCE_DIR", "REL_POTFILES_SOURCE_DIR"))) FILE_NAME_POT = property(*(_gen_get_set_path("I18N_DIR", "REL_FILE_NAME_POT"))) MO_PATH_ROOT = property(*(_gen_get_set_path("I18N_DIR", "REL_MO_PATH_ROOT"))) diff --git a/release/scripts/modules/bl_i18n_utils/utils.py b/release/scripts/modules/bl_i18n_utils/utils.py index 524fef909e8..5fdb6b88cbf 100644 --- a/release/scripts/modules/bl_i18n_utils/utils.py +++ b/release/scripts/modules/bl_i18n_utils/utils.py @@ -162,7 +162,7 @@ def get_po_files_from_dir(root_dir, langs=set()): yield uid, po_file -def enable_addons(addons={}, support={}, disable=False, check_only=False): +def enable_addons(addons=None, support=None, disable=False, check_only=False): """ Enable (or disable) addons based either on a set of names, or a set of 'support' types. Returns the list of all affected addons (as fake modules)! @@ -170,6 +170,11 @@ def enable_addons(addons={}, support={}, disable=False, check_only=False): """ import addon_utils + if addons is None: + addons = {} + if support is None: + support = {} + userpref = bpy.context.user_preferences used_ext = {ext.module for ext in userpref.addons} @@ -212,13 +217,13 @@ class I18nMessage: __slots__ = ("msgctxt_lines", "msgid_lines", "msgstr_lines", "comment_lines", "is_fuzzy", "is_commented", "settings") - def __init__(self, msgctxt_lines=[], msgid_lines=[], msgstr_lines=[], comment_lines=[], + def __init__(self, msgctxt_lines=None, msgid_lines=None, msgstr_lines=None, comment_lines=None, is_commented=False, is_fuzzy=False, settings=settings): self.settings = settings - self.msgctxt_lines = msgctxt_lines - self.msgid_lines = msgid_lines - self.msgstr_lines = msgstr_lines - self.comment_lines = comment_lines + self.msgctxt_lines = msgctxt_lines or [] + self.msgid_lines = msgid_lines or [] + self.msgstr_lines = msgstr_lines or [] + self.comment_lines = comment_lines or [] self.is_fuzzy = is_fuzzy self.is_commented = is_commented @@ -976,13 +981,13 @@ class I18nMessages: def write(self, kind, dest): self.writers[kind](self, dest) - def write_messages_to_po(self, fname): + def write_messages_to_po(self, fname, compact=False): """ Write messages in fname po file. """ default_context = self.settings.DEFAULT_CONTEXT - def _write(self, f): + def _write(self, f, compact): _msgctxt = self.settings.PO_MSGCTXT _msgid = self.settings.PO_MSGID _msgstr = self.settings.PO_MSGSTR @@ -991,9 +996,12 @@ class I18nMessages: self.escape() for num, msg in enumerate(self.msgs.values()): - f.write("\n".join(msg.comment_lines)) + if compact and (msg.is_commented or msg.is_fuzzy or not msg.msgstr_lines): + continue + if not compact: + f.write("\n".join(msg.comment_lines)) # Only mark as fuzzy if msgstr is not empty! - if msg.is_fuzzy and msg.msgstr: + if msg.is_fuzzy and msg.msgstr_lines: f.write("\n" + self.settings.PO_COMMENT_FUZZY) _p = _comm if msg.is_commented else "" chunks = [] @@ -1030,10 +1038,10 @@ class I18nMessages: self.normalize(max_len=0) # No wrapping for now... if isinstance(fname, str): with open(fname, 'w', encoding="utf-8") as f: - _write(self, f) + _write(self, f, compact) # Else assume fname is already a file(like) object! else: - _write(self, fname) + _write(self, fname, compact) def write_messages_to_mo(self, fname): """ @@ -1112,6 +1120,7 @@ class I18nMessages: writers = { "PO": write_messages_to_po, + "PO_COMPACT": lambda s, fn: s.write_messages_to_po(fn, True), "MO": write_messages_to_mo, } diff --git a/release/scripts/modules/bl_i18n_utils/utils_languages_menu.py b/release/scripts/modules/bl_i18n_utils/utils_languages_menu.py index 24255d9be61..4f499476ad9 100755 --- a/release/scripts/modules/bl_i18n_utils/utils_languages_menu.py +++ b/release/scripts/modules/bl_i18n_utils/utils_languages_menu.py @@ -95,3 +95,5 @@ def gen_menu_file(stats, settings): data_lines.append("# {} #{}:{}:{}".format(FLAG_MESSAGES[flag], uid_num, label, uid)) with open(os.path.join(settings.TRUNK_MO_DIR, settings.LANGUAGES_FILE), 'w') as f: f.write("\n".join(data_lines)) + with open(os.path.join(settings.GIT_I18N_ROOT, settings.LANGUAGES_FILE), 'w') as f: + f.write("\n".join(data_lines)) diff --git a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py index cdb3a3fedb5..42a23c8c041 100644 --- a/release/scripts/modules/bl_i18n_utils/utils_spell_check.py +++ b/release/scripts/modules/bl_i18n_utils/utils_spell_check.py @@ -125,6 +125,7 @@ class SpellChecker: "multisampling", "multitexture", "multiuser", + "multiview", "namespace", "keyconfig", "online", @@ -186,6 +187,7 @@ class SpellChecker: "unhide", "unindent", "unkeyed", + "unmute", "unpremultiply", "unprojected", "unreacted", diff --git a/release/scripts/modules/bpy/path.py b/release/scripts/modules/bpy/path.py index 25a6c7242e0..d5b64933165 100644 --- a/release/scripts/modules/bpy/path.py +++ b/release/scripts/modules/bpy/path.py @@ -116,7 +116,11 @@ def is_subdir(path, directory): from os.path import normpath, normcase path = normpath(normcase(path)) directory = normpath(normcase(directory)) - return path.startswith(directory) + if len(path) > len(directory): + if path.startswith(directory): + sep = ord(_os.sep) if isinstance(directory, bytes) else _os.sep + return (path[len(directory)] == sep) + return False def clean_name(name, replace="_"): @@ -128,23 +132,47 @@ def clean_name(name, replace="_"): or the replace argument if defined. """ - bad_chars = ("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" - "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" - "\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c" - "\x2e\x2f\x3a\x3b\x3c\x3d\x3e\x3f\x40\x5b\x5c\x5d\x5e\x60\x7b" - "\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a" - "\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99" - "\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8" - "\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7" - "\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6" - "\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5" - "\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4" - "\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3" - "\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe") - - for ch in bad_chars: - name = name.replace(ch, replace) - return name + if replace != "_": + if len(replace) != 1 or ord(replace) > 255: + raise ValueError("Value must be a single ascii character") + + def maketrans_init(): + trans_cache = clean_name._trans_cache + trans = trans_cache.get(replace) + if trans is None: + bad_chars = ( + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2e, 0x2f, 0x3a, + 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x5b, 0x5c, + 0x5d, 0x5e, 0x60, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + ) + trans = str.maketrans({char: replace for char in bad_chars}) + trans_cache[replace] = trans + return trans + + trans = maketrans_init() + return name.translate(trans) +clean_name._trans_cache = {} def _clean_utf8(name): @@ -222,7 +250,8 @@ def resolve_ncase(path): if _os.path.isdir(dirpath): try: files = _os.listdir(dirpath) - except PermissionError: # We might not have the permission to list dirpath... + except PermissionError: + # We might not have the permission to list dirpath... return path, False else: return path, False diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils/__init__.py index 5f235ae3958..7a1224db226 100644 --- a/release/scripts/modules/bpy/utils.py +++ b/release/scripts/modules/bpy/utils/__init__.py @@ -38,6 +38,7 @@ __all__ = ( "unregister_manual_map", "make_rna_paths", "manual_map", + "previews", "resource_path", "script_path_user", "script_path_pref", @@ -160,7 +161,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): # modification time changes. This `won't` work for packages so... # its not perfect. for module_name in [ext.module for ext in _user_preferences.addons]: - _addon_utils.disable(module_name, default_set=False) + _addon_utils.disable(module_name) def register_module_call(mod): register = getattr(mod, "register", None) @@ -640,10 +641,10 @@ def unregister_module(module, verbose=False): # we start with the built-in default mapping def _blender_default_map(): import sys - import rna_wiki_reference as ref_mod + import rna_manual_reference as ref_mod ret = (ref_mod.url_manual_prefix, ref_mod.url_manual_mapping) # avoid storing in memory - del sys.modules["rna_wiki_reference"] + del sys.modules["rna_manual_reference"] return ret # hooks for doc lookups diff --git a/release/scripts/modules/bpy/utils/previews.py b/release/scripts/modules/bpy/utils/previews.py new file mode 100644 index 00000000000..965971139e4 --- /dev/null +++ b/release/scripts/modules/bpy/utils/previews.py @@ -0,0 +1,153 @@ +# ##### 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 ##### + +# <pep8 compliant> + +""" +This module contains utility functions to handle custom previews. + +It behaves as a high-level 'cached' previews manager. + +This allows scripts to generate their own previews, and use them as icons in UI widgets +('icon_value' for UILayout functions). + + +Custom Icon Example +------------------- + +.. literalinclude:: ../../../release/scripts/templates_py/ui_previews_custom_icon.py +""" + +__all__ = ( + "new", + "remove", + "ImagePreviewCollection", + ) + +import _bpy +_utils_previews = _bpy._utils_previews +del _bpy + + +_uuid_open = set() + + +# High-level previews manager. +# not accessed directly +class ImagePreviewCollection(dict): + """ + Dictionary-like class of previews. + + This is a subclass of Python's built-in dict type, + used to store multiple image previews. + + .. note:: + + - instance with :mod:`bpy.utils.previews.new` + - keys must be ``str`` type. + - values will be :class:`bpy.types.ImagePreview` + """ + + # Internal notes: + # - Blender's internal 'PreviewImage' struct uses 'self._uuid' prefix. + + def __init__(self): + super().__init__() + self._uuid = hex(id(self)) + _uuid_open.add(self._uuid) + + def __del__(self): + if self._uuid not in _uuid_open: + return + + raise ResourceWarning( + "<%s id=%s[%d]>: left open, remove with " + "'bpy.utils.previews.remove()'" % + (self.__class__.__name__, self._uuid, len(self))) + self.close() + + def _gen_key(self, name): + return ":".join((self._uuid, name)) + + def new(self, name): + if name in self: + raise KeyException("key %r already exists") + p = self[name] = _utils_previews.new( + self._gen_key(name)) + return p + new.__doc__ = _utils_previews.new.__doc__ + + def load(self, name, path, path_type, force_reload=False): + if name in self: + raise KeyException("key %r already exists") + p = self[name] = _utils_previews.load( + self._gen_key(name), path, path_type, force_reload) + return p + load.__doc__ = _utils_previews.load.__doc__ + + def clear(self): + """Clear all previews.""" + for name in self.keys(): + _utils_previews.release(self._gen_key(name)) + super().clear() + + def close(self): + """Close the collection and clear all previews.""" + self.clear() + _uuid_open.remove(self._uuid) + + def __delitem__(self, key): + _utils_previews.release(self._gen_key(key)) + super().__delitem__(key) + + def __repr__(self): + return "<%s id=%s[%d], %s>" % ( + self.__class__.__name__, + self._uuid, + len(self), + super().__repr__()) + + +def new(): + """ + :return: a new preview collection. + :rtype: :class:`ImagePreviewCollection` + """ + + return ImagePreviewCollection() + + +def remove(pcoll): + """ + Remove the specified previews collection. + + :arg pcoll: Preview collection to close. + :type pcoll: :class:`ImagePreviewCollection` + """ + pcoll.close() + + +# don't complain about resources on exit (only unregister) +import atexit + + +def exit_clear_warning(): + del ImagePreviewCollection.__del__ + +atexit.register(exit_clear_warning) +del atexit, exit_clear_warning diff --git a/release/scripts/modules/bpy_extras/io_utils.py b/release/scripts/modules/bpy_extras/io_utils.py index 81de0d7c6f0..65ccc3f8dc3 100644 --- a/release/scripts/modules/bpy_extras/io_utils.py +++ b/release/scripts/modules/bpy_extras/io_utils.py @@ -21,7 +21,7 @@ __all__ = ( "ExportHelper", "ImportHelper", - "OrientationHelper", + "orientation_helper_factory", "axis_conversion", "axis_conversion_ensure", "create_derived_objects", @@ -35,7 +35,11 @@ __all__ = ( ) import bpy -from bpy.props import StringProperty, BoolProperty, EnumProperty +from bpy.props import ( + StringProperty, + BoolProperty, + EnumProperty, + ) def _check_axis_conversion(op): @@ -61,6 +65,12 @@ class ExportHelper: options={'HIDDEN'}, ) + # needed for mix-ins + order = [ + "filepath", + "check_existing", + ] + # subclasses can override with decorator # True == use ext, False == no ext, None == do nothing. check_extension = True @@ -109,6 +119,11 @@ class ImportHelper: subtype='FILE_PATH', ) + # needed for mix-ins + order = [ + "filepath", + ] + def invoke(self, context, event): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} @@ -117,13 +132,14 @@ class ImportHelper: return _check_axis_conversion(self) -class OrientationHelper: +def orientation_helper_factory(name, axis_forward='Y', axis_up='Z'): + members = {} def _update_axis_forward(self, context): if self.axis_forward[-1] == self.axis_up[-1]: self.axis_up = self.axis_up[0:-1] + 'XYZ'[('XYZ'.index(self.axis_up[-1]) + 1) % 3] - axis_forward = EnumProperty( + members['axis_forward'] = EnumProperty( name="Forward", items=(('X', "X Forward", ""), ('Y', "Y Forward", ""), @@ -132,7 +148,7 @@ class OrientationHelper: ('-Y', "-Y Forward", ""), ('-Z', "-Z Forward", ""), ), - default='-Z', + default=axis_forward, update=_update_axis_forward, ) @@ -140,7 +156,7 @@ class OrientationHelper: if self.axis_up[-1] == self.axis_forward[-1]: self.axis_forward = self.axis_forward[0:-1] + 'XYZ'[('XYZ'.index(self.axis_forward[-1]) + 1) % 3] - axis_up = EnumProperty( + members['axis_up'] = EnumProperty( name="Up", items=(('X', "X Up", ""), ('Y', "Y Up", ""), @@ -149,10 +165,17 @@ class OrientationHelper: ('-Y', "-Y Up", ""), ('-Z', "-Z Up", ""), ), - default='Y', + default=axis_up, update=_update_axis_up, ) + members["order"] = [ + "axis_forward", + "axis_up", + ] + + return type(name, (object,), members) + # Axis conversion function, not pretty LUT # use lookup table to convert between any axis @@ -349,7 +372,7 @@ def unpack_list(list_of_tuples): # same as above except that it adds 0 for triangle faces def unpack_face_list(list_of_tuples): - #allocate the entire list + # allocate the entire list flat_ls = [0] * (len(list_of_tuples) * 4) i = 0 diff --git a/release/scripts/modules/bpy_extras/object_utils.py b/release/scripts/modules/bpy_extras/object_utils.py index 13ef86b23c0..78fb6aa8fa2 100644 --- a/release/scripts/modules/bpy_extras/object_utils.py +++ b/release/scripts/modules/bpy_extras/object_utils.py @@ -31,7 +31,10 @@ __all__ = ( import bpy -from bpy.props import BoolProperty, FloatVectorProperty +from bpy.props import ( + BoolProperty, + FloatVectorProperty, + ) def add_object_align_init(context, operator): @@ -171,7 +174,7 @@ def object_data_add(context, obdata, operator=None, use_active_layer=True, name= obj_act.select = True scene.update() # apply location - #scene.objects.active = obj_new + # scene.objects.active = obj_new bpy.ops.object.join() # join into the active. if obdata: @@ -287,7 +290,8 @@ def world_to_camera_view(scene, obj, coord): Returns the camera space coords for a 3d point. (also known as: normalized device coordinates - NDC). - Where (0, 0) is the bottom left and (1, 1) is the top right of the camera frame. + Where (0, 0) is the bottom left and (1, 1) + is the top right of the camera frame. values outside 0-1 are also supported. A negative 'z' value means the point is behind the camera. @@ -300,7 +304,8 @@ def world_to_camera_view(scene, obj, coord): :type obj: :class:`bpy.types.Object` :arg coord: World space location. :type coord: :class:`mathutils.Vector` - :return: a vector where X and Y map to the view plane and Z is the depth on the view axis. + :return: a vector where X and Y map to the view plane and + Z is the depth on the view axis. :rtype: :class:`mathutils.Vector` """ from mathutils import Vector diff --git a/release/scripts/modules/bpy_extras/view3d_utils.py b/release/scripts/modules/bpy_extras/view3d_utils.py index ec4a395f1e1..4aa06262970 100644 --- a/release/scripts/modules/bpy_extras/view3d_utils.py +++ b/release/scripts/modules/bpy_extras/view3d_utils.py @@ -69,11 +69,13 @@ def region_2d_to_origin_3d(region, rv3d, coord, clamp=None): .. note:: - Orthographic views have a less obvious origin, the far clip is used to define the viewport near/far extents. - Since far clip can be a very large value, the result may give with numeric precision issues. + Orthographic views have a less obvious origin, + the far clip is used to define the viewport near/far extents. + Since far clip can be a very large value, + the result may give with numeric precision issues. - To avoid this problem, you can optionally clamp the far clip to a smaller value - based on the data you're operating on. + To avoid this problem, you can optionally clamp the far clip to a + smaller value based on the data you're operating on. :arg region: region of the 3D viewport, typically bpy.context.region. :type region: :class:`bpy.types.Region` @@ -160,7 +162,7 @@ def region_2d_to_location_3d(region, rv3d, coord, depth_location): )[0] -def location_3d_to_region_2d(region, rv3d, coord): +def location_3d_to_region_2d(region, rv3d, coord, default=None): """ Return the *region* relative 2d location of a 3d position. @@ -170,8 +172,10 @@ def location_3d_to_region_2d(region, rv3d, coord): :type rv3d: :class:`bpy.types.RegionView3D` :arg coord: 3d worldspace location. :type coord: 3d vector + :arg default: Return this value if ``coord`` + is behind the origin of a perspective view. :return: 2d location - :rtype: :class:`mathutils.Vector` + :rtype: :class:`mathutils.Vector` or ``default`` argument. """ from mathutils import Vector @@ -184,4 +188,4 @@ def location_3d_to_region_2d(region, rv3d, coord): height_half + height_half * (prj.y / prj.w), )) else: - return None + return default diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index c7ec7e1e54a..92dbd2dbd0e 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -715,7 +715,7 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): __slots__ = () def path_menu(self, searchpaths, operator, - props_default={}, filter_ext=None): + props_default=None, filter_ext=None): layout = self.layout # hard coded to set the operators 'filepath' to the filename. @@ -745,8 +745,9 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): text=bpy.path.display_name(f), translate=False) - for attr, value in props_default.items(): - setattr(props, attr, value) + if props_default is not None: + for attr, value in props_default.items(): + setattr(props, attr, value) props.filepath = filepath if operator == "script.execute_preset": @@ -754,14 +755,21 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): def draw_preset(self, context): """ - Define these on the subclass - - preset_operator - - preset_subdir + Define these on the subclass: + - preset_operator (string) + - preset_subdir (string) + + Optionally: + - preset_extensions (set of strings) + - preset_operator_defaults (dict of keyword args) """ import bpy + ext_valid = getattr(self, "preset_extensions", {".py", ".xml"}) + props_default = getattr(self, "preset_operator_defaults", None) self.path_menu(bpy.utils.preset_paths(self.preset_subdir), self.preset_operator, - filter_ext=lambda ext: ext.lower() in {".py", ".xml"}) + props_default=props_default, + filter_ext=lambda ext: ext.lower() in ext_valid) @classmethod def draw_collapsible(cls, context, layout): @@ -773,24 +781,6 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta): layout.menu(cls.__name__, icon='COLLAPSEMENU') -class Region(StructRNA): - __slots__ = () - - def callback_add(self, cb, args, draw_mode): - """ - Append a draw function to this region, - deprecated, instead use bpy.types.SpaceView3D.draw_handler_add - """ - for area in self.id_data.areas: - for region in area.regions: - if region == self: - spacetype = type(area.spaces[0]) - return spacetype.draw_handler_add(cb, args, self.type, - draw_mode) - - return None - - class NodeTree(bpy_types.ID, metaclass=RNAMetaPropGroup): __slots__ = () diff --git a/release/scripts/modules/nodeitems_utils.py b/release/scripts/modules/nodeitems_utils.py index 2882a08fbd4..2f69ea84386 100644 --- a/release/scripts/modules/nodeitems_utils.py +++ b/release/scripts/modules/nodeitems_utils.py @@ -37,13 +37,17 @@ class NodeCategory: else: def items_gen(context): for item in items: - if item.poll is None or item.poll(context): + if item.poll is None or context is None or item.poll(context): yield item self.items = items_gen class NodeItem: - def __init__(self, nodetype, label=None, settings={}, poll=None): + def __init__(self, nodetype, label=None, settings=None, poll=None): + + if settings is None: + settings = {} + self.nodetype = nodetype self._label = label self.settings = settings @@ -136,7 +140,7 @@ def register_node_categories(identifier, cat_list): def node_categories_iter(context): for cat_type in _node_categories.values(): for cat in cat_type[0]: - if cat.poll and cat.poll(context): + if cat.poll and ((context is None) or cat.poll(context)): yield cat diff --git a/release/scripts/modules/rna_info.py b/release/scripts/modules/rna_info.py index 353362ed168..dae262e93d7 100644 --- a/release/scripts/modules/rna_info.py +++ b/release/scripts/modules/rna_info.py @@ -268,7 +268,8 @@ class InfoPropertyRNA: self.default_str = "\"%s\"" % self.default elif self.type == "enum": if self.is_enum_flag: - self.default_str = "%r" % self.default # repr or set() + # self.default_str = "%r" % self.default # repr or set() + self.default_str = "{%s}" % repr(list(sorted(self.default)))[1:-1] else: self.default_str = "'%s'" % self.default elif self.array_length: @@ -695,7 +696,7 @@ def BuildRNAInfo(): return InfoStructRNA.global_lookup, InfoFunctionRNA.global_lookup, InfoOperatorRNA.global_lookup, InfoPropertyRNA.global_lookup -if __name__ == "__main__": +def main(): import rna_info struct = rna_info.BuildRNAInfo()[0] data = [] @@ -723,3 +724,7 @@ if __name__ == "__main__": else: text = bpy.data.texts.new(name="api.py") text.from_string(data) + + +if __name__ == "__main__": + main() diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py index f4649453b46..44722fa7162 100644 --- a/release/scripts/modules/rna_prop_ui.py +++ b/release/scripts/modules/rna_prop_ui.py @@ -32,6 +32,13 @@ def rna_idprop_ui_get(item, create=True): return None +def rna_idprop_ui_del(item): + try: + del item['_RNA_UI'] + except KeyError: + pass + + def rna_idprop_ui_prop_get(item, prop, create=True): rna_ui = rna_idprop_ui_get(item, create) @@ -46,7 +53,7 @@ def rna_idprop_ui_prop_get(item, prop, create=True): return rna_ui[prop] -def rna_idprop_ui_prop_clear(item, prop): +def rna_idprop_ui_prop_clear(item, prop, remove=True): rna_ui = rna_idprop_ui_get(item, False) if rna_ui is None: @@ -54,8 +61,10 @@ def rna_idprop_ui_prop_clear(item, prop): try: del rna_ui[prop] - except: + except KeyError: pass + if remove and len(item.keys()) == 1: + rna_idprop_ui_del(item) def rna_idprop_context_value(context, context_member, property_type): diff --git a/release/scripts/modules/rna_xml.py b/release/scripts/modules/rna_xml.py index 729d6238ac3..ad4809efbe1 100644 --- a/release/scripts/modules/rna_xml.py +++ b/release/scripts/modules/rna_xml.py @@ -32,7 +32,7 @@ def build_property_typemap(skip_classes, skip_typemap): if issubclass(cls, skip_classes): continue - ## to support skip-save we cant get all props + # # to support skip-save we cant get all props # properties = cls.bl_rna.properties.keys() properties = [] for prop_id, prop in cls.bl_rna.properties.items(): |