Cleanup: use f-string
[blender.git] / release / scripts / modules / bpy / utils / __init__.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
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.
7 #
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.
12 #
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.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 """
22 This module contains utility functions specific to blender but
23 not associated with blenders internal data.
24 """
25
26 __all__ = (
27     "blend_paths",
28     "escape_identifier",
29     "keyconfig_set",
30     "load_scripts",
31     "modules_from_path",
32     "preset_find",
33     "preset_paths",
34     "refresh_script_paths",
35     "app_template_paths",
36     "register_class",
37     "register_module",
38     "register_manual_map",
39     "unregister_manual_map",
40     "register_classes_factory",
41     "register_submodule_factory",
42     "make_rna_paths",
43     "manual_map",
44     "previews",
45     "resource_path",
46     "script_path_user",
47     "script_path_pref",
48     "script_paths",
49     "smpte_from_frame",
50     "smpte_from_seconds",
51     "units",
52     "unregister_class",
53     "unregister_module",
54     "user_resource",
55 )
56
57 from _bpy import (
58     _utils_units as units,
59     blend_paths,
60     escape_identifier,
61     register_class,
62     resource_path,
63     script_paths as _bpy_script_paths,
64     unregister_class,
65     user_resource as _user_resource,
66 )
67
68 import bpy as _bpy
69 import os as _os
70 import sys as _sys
71
72 import addon_utils as _addon_utils
73
74 _user_preferences = _bpy.context.user_preferences
75 _script_module_dirs = "startup", "modules"
76 _is_factory_startup = _bpy.app.factory_startup
77
78
79 def _test_import(module_name, loaded_modules):
80     use_time = _bpy.app.debug_python
81
82     if module_name in loaded_modules:
83         return None
84     if "." in module_name:
85         print("Ignoring '%s', can't import files containing "
86               "multiple periods" % module_name)
87         return None
88
89     if use_time:
90         import time
91         t = time.time()
92
93     try:
94         mod = __import__(module_name)
95     except:
96         import traceback
97         traceback.print_exc()
98         return None
99
100     if use_time:
101         print("time %s %.4f" % (module_name, time.time() - t))
102
103     loaded_modules.add(mod.__name__)  # should match mod.__name__ too
104     return mod
105
106
107 def _sys_path_ensure(path):
108     if path not in _sys.path:  # reloading would add twice
109         _sys.path.insert(0, path)
110
111
112 def modules_from_path(path, loaded_modules):
113     """
114     Load all modules in a path and return them as a list.
115
116     :arg path: this path is scanned for scripts and packages.
117     :type path: string
118     :arg loaded_modules: already loaded module names, files matching these
119        names will be ignored.
120     :type loaded_modules: set
121     :return: all loaded modules.
122     :rtype: list
123     """
124     modules = []
125
126     for mod_name, mod_path in _bpy.path.module_names(path):
127         mod = _test_import(mod_name, loaded_modules)
128         if mod:
129             modules.append(mod)
130
131     return modules
132
133
134 _global_loaded_modules = []  # store loaded module names for reloading.
135 import bpy_types as _bpy_types  # keep for comparisons, never ever reload this.
136
137
138 def load_scripts(reload_scripts=False, refresh_scripts=False):
139     """
140     Load scripts and run each modules register function.
141
142     :arg reload_scripts: Causes all scripts to have their unregister method
143        called before loading.
144     :type reload_scripts: bool
145     :arg refresh_scripts: only load scripts which are not already loaded
146        as modules.
147     :type refresh_scripts: bool
148     """
149     use_time = use_class_register_check = _bpy.app.debug_python
150     use_user = not _is_factory_startup
151
152     if use_time:
153         import time
154         t_main = time.time()
155
156     loaded_modules = set()
157
158     if refresh_scripts:
159         original_modules = _sys.modules.values()
160
161     if reload_scripts:
162         # just unload, don't change user defaults, this means we can sync
163         # to reload. note that they will only actually reload of the
164         # modification time changes. This `won't` work for packages so...
165         # its not perfect.
166         for module_name in [ext.module for ext in _user_preferences.addons]:
167             _addon_utils.disable(module_name)
168
169         # *AFTER* unregistering all add-ons, otherwise all calls to
170         # unregister_module() will silently fail (do nothing).
171         _bpy_types.TypeMap.clear()
172
173     def register_module_call(mod):
174         register = getattr(mod, "register", None)
175         if register:
176             try:
177                 register()
178             except:
179                 import traceback
180                 traceback.print_exc()
181         else:
182             print("\nWarning! '%s' has no register function, "
183                   "this is now a requirement for registerable scripts" %
184                   mod.__file__)
185
186     def unregister_module_call(mod):
187         unregister = getattr(mod, "unregister", None)
188         if unregister:
189             try:
190                 unregister()
191             except:
192                 import traceback
193                 traceback.print_exc()
194
195     def test_reload(mod):
196         import importlib
197         # reloading this causes internal errors
198         # because the classes from this module are stored internally
199         # possibly to refresh internal references too but for now, best not to.
200         if mod == _bpy_types:
201             return mod
202
203         try:
204             return importlib.reload(mod)
205         except:
206             import traceback
207             traceback.print_exc()
208
209     def test_register(mod):
210
211         if refresh_scripts and mod in original_modules:
212             return
213
214         if reload_scripts and mod:
215             print("Reloading:", mod)
216             mod = test_reload(mod)
217
218         if mod:
219             register_module_call(mod)
220             _global_loaded_modules.append(mod.__name__)
221
222     if reload_scripts:
223
224         # module names -> modules
225         _global_loaded_modules[:] = [_sys.modules[mod_name]
226                                      for mod_name in _global_loaded_modules]
227
228         # loop over and unload all scripts
229         _global_loaded_modules.reverse()
230         for mod in _global_loaded_modules:
231             unregister_module_call(mod)
232
233         for mod in _global_loaded_modules:
234             test_reload(mod)
235
236         del _global_loaded_modules[:]
237
238     from bpy_restrict_state import RestrictBlend
239
240     with RestrictBlend():
241         for base_path in script_paths(use_user=use_user):
242             for path_subdir in _script_module_dirs:
243                 path = _os.path.join(base_path, path_subdir)
244                 if _os.path.isdir(path):
245                     _sys_path_ensure(path)
246
247                     # only add this to sys.modules, don't run
248                     if path_subdir == "modules":
249                         continue
250
251                     for mod in modules_from_path(path, loaded_modules):
252                         test_register(mod)
253
254     # load template (if set)
255     if any(_bpy.utils.app_template_paths()):
256         import bl_app_template_utils
257         bl_app_template_utils.reset(reload_scripts=reload_scripts)
258         del bl_app_template_utils
259
260     # deal with addons separately
261     _initialize = getattr(_addon_utils, "_initialize", None)
262     if _initialize is not None:
263         # first time, use fast-path
264         _initialize()
265         del _addon_utils._initialize
266     else:
267         _addon_utils.reset_all(reload_scripts=reload_scripts)
268     del _initialize
269
270     # run the active integration preset
271     filepath = preset_find(_user_preferences.inputs.active_keyconfig,
272                            "keyconfig")
273
274     if filepath:
275         keyconfig_set(filepath)
276
277     if reload_scripts:
278         import gc
279         print("gc.collect() -> %d" % gc.collect())
280
281     if use_time:
282         print("Python Script Load Time %.4f" % (time.time() - t_main))
283
284     if use_class_register_check:
285         for cls in _bpy.types.bpy_struct.__subclasses__():
286             if getattr(cls, "is_registered", False):
287                 for subcls in cls.__subclasses__():
288                     if not subcls.is_registered:
289                         print(
290                             "Warning, unregistered class: %s(%s)" %
291                             (subcls.__name__, cls.__name__)
292                         )
293
294
295 # base scripts
296 _scripts = (
297     _os.path.dirname(_os.path.dirname(_os.path.dirname(__file__))),
298 )
299
300
301 def script_path_user():
302     """returns the env var and falls back to home dir or None"""
303     path = _user_resource('SCRIPTS')
304     return _os.path.normpath(path) if path else None
305
306
307 def script_path_pref():
308     """returns the user preference or None"""
309     path = _user_preferences.filepaths.script_directory
310     return _os.path.normpath(path) if path else None
311
312
313 def script_paths(subdir=None, user_pref=True, check_all=False, use_user=True):
314     """
315     Returns a list of valid script paths.
316
317     :arg subdir: Optional subdir.
318     :type subdir: string
319     :arg user_pref: Include the user preference script path.
320     :type user_pref: bool
321     :arg check_all: Include local, user and system paths rather just the paths
322        blender uses.
323     :type check_all: bool
324     :return: script paths.
325     :rtype: list
326     """
327     scripts = list(_scripts)
328
329     # Only paths Blender uses.
330     #
331     # Needed this is needed even when 'check_all' is enabled,
332     # so the 'BLENDER_SYSTEM_SCRIPTS' environment variable will be used.
333     base_paths = _bpy_script_paths()
334
335     # Defined to be (system, user) so we can skip the second if needed.
336     if not use_user:
337         base_paths = base_paths[:1]
338
339     if check_all:
340         # All possible paths, no duplicates, keep order.
341         if use_user:
342             test_paths = ('LOCAL', 'USER', 'SYSTEM')
343         else:
344             test_paths = ('LOCAL', 'SYSTEM')
345
346         base_paths = (
347             *(path for path in (
348                 _os.path.join(resource_path(res), "scripts")
349                 for res in test_paths) if path not in base_paths),
350             *base_paths,
351         )
352
353     if use_user:
354         test_paths = (*base_paths, script_path_user(), script_path_pref())
355     else:
356         test_paths = (*base_paths, script_path_pref())
357
358     for path in test_paths:
359         if path:
360             path = _os.path.normpath(path)
361             if path not in scripts and _os.path.isdir(path):
362                 scripts.append(path)
363
364     if subdir is None:
365         return scripts
366
367     scripts_subdir = []
368     for path in scripts:
369         path_subdir = _os.path.join(path, subdir)
370         if _os.path.isdir(path_subdir):
371             scripts_subdir.append(path_subdir)
372
373     return scripts_subdir
374
375
376 def refresh_script_paths():
377     """
378     Run this after creating new script paths to update sys.path
379     """
380
381     for base_path in script_paths():
382         for path_subdir in _script_module_dirs:
383             path = _os.path.join(base_path, path_subdir)
384             if _os.path.isdir(path):
385                 _sys_path_ensure(path)
386
387     for path in _addon_utils.paths():
388         _sys_path_ensure(path)
389         path = _os.path.join(path, "modules")
390         if _os.path.isdir(path):
391             _sys_path_ensure(path)
392
393
394 def app_template_paths(subdir=None):
395     """
396     Returns valid application template paths.
397
398     :arg subdir: Optional subdir.
399     :type subdir: string
400     :return: app template paths.
401     :rtype: generator
402     """
403     # Note: keep in sync with: Blender's BKE_appdir_app_template_any
404
405     subdir_tuple = (subdir,) if subdir is not None else ()
406
407     # Avoid adding 'bl_app_templates_system' twice.
408     # Either we have a portable build or an installed system build.
409     for resource_type, module_name in (
410             ('USER', "bl_app_templates_user"),
411             ('LOCAL', "bl_app_templates_system"),
412             ('SYSTEM', "bl_app_templates_system"),
413     ):
414         path = resource_path(resource_type)
415         if path:
416             path = _os.path.join(
417                 *(path, "scripts", "startup", module_name, *subdir_tuple))
418             if _os.path.isdir(path):
419                 yield path
420                 # Only load LOCAL or SYSTEM (never both).
421                 if resource_type == 'LOCAL':
422                     break
423
424
425 def preset_paths(subdir):
426     """
427     Returns a list of paths for a specific preset.
428
429     :arg subdir: preset subdirectory (must not be an absolute path).
430     :type subdir: string
431     :return: script paths.
432     :rtype: list
433     """
434     dirs = []
435     for path in script_paths("presets", check_all=True):
436         directory = _os.path.join(path, subdir)
437         if not directory.startswith(path):
438             raise Exception("invalid subdir given %r" % subdir)
439         elif _os.path.isdir(directory):
440             dirs.append(directory)
441
442     # Find addons preset paths
443     for path in _addon_utils.paths():
444         directory = _os.path.join(path, "presets", subdir)
445         if _os.path.isdir(directory):
446             dirs.append(directory)
447
448     return dirs
449
450
451 def smpte_from_seconds(time, fps=None):
452     """
453     Returns an SMPTE formatted string from the *time*:
454     ``HH:MM:SS:FF``.
455
456     If the *fps* is not given the current scene is used.
457
458     :arg time: time in seconds.
459     :type time: int, float or ``datetime.timedelta``.
460     :return: the frame string.
461     :rtype: string
462     """
463
464     return smpte_from_frame(time_to_frame(time, fps=fps), fps)
465
466
467 def smpte_from_frame(frame, fps=None, fps_base=None):
468     """
469     Returns an SMPTE formatted string from the *frame*:
470     ``HH:MM:SS:FF``.
471
472     If *fps* and *fps_base* are not given the current scene is used.
473
474     :arg frame: frame number.
475     :type frame: int or float.
476     :return: the frame string.
477     :rtype: string
478     """
479
480     if fps is None:
481         fps = _bpy.context.scene.render.fps
482
483     if fps_base is None:
484         fps_base = _bpy.context.scene.render.fps_base
485
486     sign = "-" if frame < 0 else ""
487     frame = abs(frame * fps_base)
488
489     return (
490         "%s%02d:%02d:%02d:%02d" % (
491         sign,
492         int(frame / (3600 * fps)),          # HH
493         int((frame / (60 * fps)) % 60),     # MM
494         int((frame / fps) % 60),            # SS
495         int(frame % fps),                   # FF
496         ))
497
498
499 def time_from_frame(frame, fps=None, fps_base=None):
500     """
501     Returns the time from a frame number .
502
503     If *fps* and *fps_base* are not given the current scene is used.
504
505     :arg frame: number.
506     :type frame: int or float.
507     :return: the time in seconds.
508     :rtype: datetime.timedelta
509     """
510
511     if fps is None:
512         fps = _bpy.context.scene.render.fps
513
514     if fps_base is None:
515         fps_base = _bpy.context.scene.render.fps_base
516
517     from datetime import timedelta
518
519     return timedelta(0, (frame * fps_base) / fps)
520
521
522 def time_to_frame(time, fps=None, fps_base=None):
523     """
524     Returns a float frame number from a time given in seconds or
525     as a datetime.timedelta object.
526
527     If *fps* and *fps_base* are not given the current scene is used.
528
529     :arg time: time in seconds.
530     :type time: number or a ``datetime.timedelta`` object
531     :return: the frame.
532     :rtype: float
533     """
534
535     if fps is None:
536         fps = _bpy.context.scene.render.fps
537
538     if fps_base is None:
539         fps_base = _bpy.context.scene.render.fps_base
540
541     from datetime import timedelta
542
543     if isinstance(time, timedelta):
544         time = time.total_seconds()
545
546     return (time / fps_base) * fps
547
548
549 def preset_find(name, preset_path, display_name=False, ext=".py"):
550     if not name:
551         return None
552
553     for directory in preset_paths(preset_path):
554
555         if display_name:
556             filename = ""
557             for fn in _os.listdir(directory):
558                 if fn.endswith(ext) and name == _bpy.path.display_name(fn):
559                     filename = fn
560                     break
561         else:
562             filename = name + ext
563
564         if filename:
565             filepath = _os.path.join(directory, filename)
566             if _os.path.exists(filepath):
567                 return filepath
568
569
570 def keyconfig_set(filepath, report=None):
571     from os.path import basename, splitext
572     from itertools import chain
573
574     if _bpy.app.debug_python:
575         print("loading preset:", filepath)
576
577     keyconfigs = _bpy.context.window_manager.keyconfigs
578
579     keyconfigs_old = keyconfigs[:]
580
581     try:
582         error_msg = ""
583         with open(filepath, 'r', encoding='utf-8') as keyfile:
584             exec(compile(keyfile.read(), filepath, "exec"),
585                  {"__file__": filepath})
586     except:
587         import traceback
588         error_msg = traceback.format_exc()
589
590     if error_msg:
591         if report is not None:
592             report({'ERROR'}, error_msg)
593         print(error_msg)
594
595     kc_new = next(chain(iter(kc for kc in keyconfigs
596                              if kc not in keyconfigs_old), (None,)))
597     if kc_new is None:
598         if report is not None:
599             report({'ERROR'}, "Failed to load keymap %r" % filepath)
600         return False
601     else:
602         kc_new.name = ""
603
604         # remove duplicates
605         name = splitext(basename(filepath))[0]
606         while True:
607             kc_dupe = keyconfigs.get(name)
608             if kc_dupe:
609                 keyconfigs.remove(kc_dupe)
610             else:
611                 break
612
613         kc_new.name = name
614         keyconfigs.active = kc_new
615         return True
616
617
618 def user_resource(resource_type, path="", create=False):
619     """
620     Return a user resource path (normally from the users home directory).
621
622     :arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
623     :type type: string
624     :arg subdir: Optional subdirectory.
625     :type subdir: string
626     :arg create: Treat the path as a directory and create
627        it if its not existing.
628     :type create: boolean
629     :return: a path.
630     :rtype: string
631     """
632
633     target_path = _user_resource(resource_type, path)
634
635     if create:
636         # should always be true.
637         if target_path:
638             # create path if not existing.
639             if not _os.path.exists(target_path):
640                 try:
641                     _os.makedirs(target_path)
642                 except:
643                     import traceback
644                     traceback.print_exc()
645                     target_path = ""
646             elif not _os.path.isdir(target_path):
647                 print("Path %r found but isn't a directory!" % target_path)
648                 target_path = ""
649
650     return target_path
651
652
653 def _bpy_module_classes(module, is_registered=False):
654     typemap_list = _bpy_types.TypeMap.get(module, ())
655     i = 0
656     while i < len(typemap_list):
657         cls_weakref = typemap_list[i]
658         cls = cls_weakref()
659
660         if cls is None:
661             del typemap_list[i]
662         else:
663             if is_registered == cls.is_registered:
664                 yield cls
665             i += 1
666
667
668 def register_module(module, verbose=False):
669     if verbose:
670         print("bpy.utils.register_module(%r): ..." % module)
671     cls = None
672     for cls in _bpy_module_classes(module, is_registered=False):
673         if verbose:
674             print("    %r" % cls)
675         try:
676             register_class(cls)
677         except:
678             print("bpy.utils.register_module(): "
679                   "failed to registering class %r" % cls)
680             import traceback
681             traceback.print_exc()
682     if verbose:
683         print("done.\n")
684     if cls is None:
685         raise Exception("register_module(%r): defines no classes" % module)
686
687
688 def unregister_module(module, verbose=False):
689     if verbose:
690         print("bpy.utils.unregister_module(%r): ..." % module)
691     for cls in _bpy_module_classes(module, is_registered=True):
692         if verbose:
693             print("    %r" % cls)
694         try:
695             unregister_class(cls)
696         except:
697             print("bpy.utils.unregister_module(): "
698                   "failed to unregistering class %r" % cls)
699             import traceback
700             traceback.print_exc()
701     if verbose:
702         print("done.\n")
703
704
705 def register_classes_factory(classes):
706     """
707     Utility function to create register and unregister functions
708     which simply registers and unregisters a sequence of classes.
709     """
710     def register():
711         from bpy.utils import register_class
712         for cls in classes:
713             register_class(cls)
714
715     def unregister():
716         from bpy.utils import unregister_class
717         for cls in reversed(classes):
718             unregister_class(cls)
719
720     return register, unregister
721
722
723 def register_submodule_factory(module_name, submodule_names):
724     """
725     Utility function to create register and unregister functions
726     which simply load submodules,
727     calling their register & unregister functions.
728
729     .. note::
730
731        Modules are registered in the order given,
732        unregistered in reverse order.
733
734     :arg module_name: The module name, typically ``__name__``.
735     :type module_name: string
736     :arg submodule_names: List of submodule names to load and unload.
737     :type submodule_names: list of strings
738     :return: register and unregister functions.
739     :rtype: tuple pair of functions
740     """
741
742     module = None
743     submodules = []
744
745     def register():
746         nonlocal module
747         module = __import__(name=module_name, fromlist=submodule_names)
748         submodules[:] = [getattr(module, name) for name in submodule_names]
749         for mod in submodules:
750             mod.register()
751
752     def unregister():
753         from sys import modules
754         for mod in reversed(submodules):
755             mod.unregister()
756             name = mod.__name__
757             delattr(module, name.partition(".")[2])
758             del modules[name]
759         submodules.clear()
760
761     return register, unregister
762
763
764 # -----------------------------------------------------------------------------
765 # Manual lookups, each function has to return a basepath and a sequence
766 # of...
767
768 # we start with the built-in default mapping
769 def _blender_default_map():
770     import rna_manual_reference as ref_mod
771     ret = (ref_mod.url_manual_prefix, ref_mod.url_manual_mapping)
772     # avoid storing in memory
773     del _sys.modules["rna_manual_reference"]
774     return ret
775
776 # hooks for doc lookups
777 _manual_map = [_blender_default_map]
778
779
780 def register_manual_map(manual_hook):
781     _manual_map.append(manual_hook)
782
783
784 def unregister_manual_map(manual_hook):
785     _manual_map.remove(manual_hook)
786
787
788 def manual_map():
789     # reverse so default is called last
790     for cb in reversed(_manual_map):
791         try:
792             prefix, url_manual_mapping = cb()
793         except:
794             print("Error calling %r" % cb)
795             import traceback
796             traceback.print_exc()
797             continue
798
799         yield prefix, url_manual_mapping
800
801
802 # Build an RNA path from struct/property/enum names.
803 def make_rna_paths(struct_name, prop_name, enum_name):
804     """
805     Create RNA "paths" from given names.
806
807     :arg struct_name: Name of a RNA struct (like e.g. "Scene").
808     :type struct_name: string
809     :arg prop_name: Name of a RNA struct's property.
810     :type prop_name: string
811     :arg enum_name: Name of a RNA enum identifier.
812     :type enum_name: string
813     :return: A triple of three "RNA paths"
814        (most_complete_path, "struct.prop", "struct.prop:'enum'").
815        If no enum_name is given, the third element will always be void.
816     :rtype: tuple of strings
817     """
818     src = src_rna = src_enum = ""
819     if struct_name:
820         if prop_name:
821             src = src_rna = ".".join((struct_name, prop_name))
822             if enum_name:
823                 src = src_enum = "%s:'%s'" % (src_rna, enum_name)
824         else:
825             src = src_rna = struct_name
826     return src, src_rna, src_enum