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