bl_app_template_utils.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  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. # <pep8-80 compliant>
  19. """
  20. Similar to ``addon_utils``, except we can only have one active at a time.
  21. In most cases users of this module will simply call 'activate'.
  22. """
  23. __all__ = (
  24. "activate",
  25. "import_from_path",
  26. "import_from_id",
  27. "reset",
  28. )
  29. import bpy as _bpy
  30. # Normally matches 'preferences.app_template_id',
  31. # but loading new preferences will get us out of sync.
  32. _app_template = {
  33. "id": "",
  34. }
  35. # instead of sys.modules
  36. # note that we only ever have one template enabled at a time
  37. # so it may not seem necessary to use this.
  38. #
  39. # However, templates may want to share between each-other,
  40. # so any loaded modules are stored here?
  41. #
  42. # Note that the ID here is the app_template_id , not the modules __name__.
  43. _modules = {}
  44. def _enable(template_id, *, handle_error=None, ignore_not_found=False):
  45. from bpy_restrict_state import RestrictBlend
  46. if handle_error is None:
  47. def handle_error(_ex):
  48. import traceback
  49. traceback.print_exc()
  50. # Split registering up into 2 steps so we can undo
  51. # if it fails par way through.
  52. # disable the context, using the context at all is
  53. # really bad while loading an template, don't do it!
  54. with RestrictBlend():
  55. # 1) try import
  56. try:
  57. mod = import_from_id(template_id, ignore_not_found=ignore_not_found)
  58. if mod is None:
  59. return None
  60. mod.__template_enabled__ = False
  61. _modules[template_id] = mod
  62. except Exception as ex:
  63. handle_error(ex)
  64. return None
  65. # 2) try run the modules register function
  66. try:
  67. mod.register()
  68. except Exception as ex:
  69. print("Exception in module register(): %r" %
  70. getattr(mod, "__file__", template_id))
  71. handle_error(ex)
  72. del _modules[template_id]
  73. return None
  74. # * OK loaded successfully! *
  75. mod.__template_enabled__ = True
  76. if _bpy.app.debug_python:
  77. print("\tapp_template_utils.enable", mod.__name__)
  78. return mod
  79. def _disable(template_id, *, handle_error=None):
  80. """
  81. Disables a template by name.
  82. :arg template_id: The name of the template and module.
  83. :type template_id: string
  84. :arg handle_error: Called in the case of an error,
  85. taking an exception argument.
  86. :type handle_error: function
  87. """
  88. if handle_error is None:
  89. def handle_error(_ex):
  90. import traceback
  91. traceback.print_exc()
  92. mod = _modules.get(template_id)
  93. if mod and getattr(mod, "__template_enabled__", False) is not False:
  94. mod.__template_enabled__ = False
  95. try:
  96. mod.unregister()
  97. except Exception as ex:
  98. print("Exception in module unregister(): %r" %
  99. getattr(mod, "__file__", template_id))
  100. handle_error(ex)
  101. else:
  102. print("\tapp_template_utils.disable: %s not %s." %
  103. (template_id, "disabled" if mod is None else "loaded"))
  104. if _bpy.app.debug_python:
  105. print("\tapp_template_utils.disable", template_id)
  106. def import_from_path(path, ignore_not_found=False):
  107. import os
  108. from importlib import import_module
  109. base_module, template_id = path.rsplit(os.sep, 2)[-2:]
  110. module_name = base_module + "." + template_id
  111. try:
  112. return import_module(module_name)
  113. except ModuleNotFoundError as ex:
  114. if ignore_not_found and ex.name == module_name:
  115. return None
  116. raise ex
  117. def import_from_id(template_id, ignore_not_found=False):
  118. import os
  119. path = next(iter(_bpy.utils.app_template_paths(template_id)), None)
  120. if path is None:
  121. if ignore_not_found:
  122. return None
  123. else:
  124. raise Exception("%r template not found!" % template_id)
  125. else:
  126. if ignore_not_found:
  127. if not os.path.exists(os.path.join(path, "__init__.py")):
  128. return None
  129. return import_from_path(path, ignore_not_found=ignore_not_found)
  130. def activate(template_id=None):
  131. template_id_prev = _app_template["id"]
  132. # not needed but may as well avoids redundant
  133. # disable/enable for all add-ons on 'File -> New'
  134. if template_id_prev == template_id:
  135. return
  136. if template_id_prev:
  137. _disable(template_id_prev)
  138. # Disable all addons, afterwards caller must reset.
  139. import addon_utils
  140. addon_utils.disable_all()
  141. # ignore_not_found so modules that don't contain scripts don't raise errors
  142. _mod = _enable(template_id, ignore_not_found=True) if template_id else None
  143. _app_template["id"] = template_id
  144. def reset(*, reload_scripts=False):
  145. """
  146. Sets default state.
  147. """
  148. template_id = _bpy.context.preferences.app_template
  149. if _bpy.app.debug_python:
  150. print("bl_app_template_utils.reset('%s')" % template_id)
  151. # TODO reload_scripts
  152. activate(template_id)