Welcome to mirror list, hosted at ThFree Co, Russian Federation.

bl_app_template_utils.py « modules « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 7db084a9a293bb91bde3e7a8ffcdd6314e7e22cc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# ##### 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-80 compliant>

"""
Similar to ``addon_utils``, except we can only have one active at a time.

In most cases users of this module will simply call 'activate'.
"""

__all__ = (
    "activate",
    "import_from_path",
    "import_from_id",
    "reset",
)

import bpy as _bpy

# Normally matches 'preferences.app_template_id',
# but loading new preferences will get us out of sync.
_app_template = {
    "id": "",
}

# instead of sys.modules
# note that we only ever have one template enabled at a time
# so it may not seem necessary to use this.
#
# However, templates may want to share between each-other,
# so any loaded modules are stored here?
#
# Note that the ID here is the app_template_id , not the modules __name__.
_modules = {}


def _enable(template_id, *, handle_error=None, ignore_not_found=False):
    from bpy_restrict_state import RestrictBlend

    if handle_error is None:
        def handle_error(_ex):
            import traceback
            traceback.print_exc()

    # Split registering up into 2 steps so we can undo
    # if it fails par way through.

    # disable the context, using the context at all is
    # really bad while loading an template, don't do it!
    with RestrictBlend():

        # 1) try import
        try:
            mod = import_from_id(template_id, ignore_not_found=ignore_not_found)
        except Exception as ex:
            handle_error(ex)
            return None

        _modules[template_id] = mod
        if mod is None:
            return None
        mod.__template_enabled__ = False

        # 2) try run the modules register function
        try:
            mod.register()
        except Exception as ex:
            print("Exception in module register(): %r" %
                  getattr(mod, "__file__", template_id))
            handle_error(ex)
            del _modules[template_id]
            return None

    # * OK loaded successfully! *
    mod.__template_enabled__ = True

    if _bpy.app.debug_python:
        print("\tapp_template_utils.enable", mod.__name__)

    return mod


def _disable(template_id, *, handle_error=None):
    """
    Disables a template by name.

    :arg template_id: The name of the template and module.
    :type template_id: string
    :arg handle_error: Called in the case of an error,
       taking an exception argument.
    :type handle_error: function
    """

    if handle_error is None:
        def handle_error(_ex):
            import traceback
            traceback.print_exc()

    mod = _modules.get(template_id, False)

    if mod is None:
        # Loaded but has no module, remove since there is no use in keeping it.
        del _modules[template_id]
    elif getattr(mod, "__template_enabled__", False) is not False:
        mod.__template_enabled__ = False

        try:
            mod.unregister()
        except Exception as ex:
            print("Exception in module unregister(): %r" %
                  getattr(mod, "__file__", template_id))
            handle_error(ex)
    else:
        print("\tapp_template_utils.disable: %s not %s." %
              (template_id, "disabled" if mod is False else "loaded"))

    if _bpy.app.debug_python:
        print("\tapp_template_utils.disable", template_id)


def import_from_path(path, ignore_not_found=False):
    import os
    from importlib import import_module
    base_module, template_id = path.rsplit(os.sep, 2)[-2:]
    module_name = base_module + "." + template_id

    try:
        return import_module(module_name)
    except ModuleNotFoundError as ex:
        if ignore_not_found and ex.name == module_name:
            return None
        raise ex


def import_from_id(template_id, ignore_not_found=False):
    import os
    path = next(iter(_bpy.utils.app_template_paths(template_id)), None)
    if path is None:
        if ignore_not_found:
            return None
        else:
            raise Exception("%r template not found!" % template_id)
    else:
        if ignore_not_found:
            if not os.path.exists(os.path.join(path, "__init__.py")):
                return None
        return import_from_path(path, ignore_not_found=ignore_not_found)


def activate(template_id=None):
    template_id_prev = _app_template["id"]

    # not needed but may as well avoids redundant
    # disable/enable for all add-ons on 'File -> New'
    if template_id_prev == template_id:
        return

    if template_id_prev:
        _disable(template_id_prev)

    # ignore_not_found so modules that don't contain scripts don't raise errors
    _mod = _enable(template_id, ignore_not_found=True) if template_id else None

    _app_template["id"] = template_id


def reset(*, reload_scripts=False):
    """
    Sets default state.
    """
    template_id = _bpy.context.preferences.app_template
    if _bpy.app.debug_python:
        print("bl_app_template_utils.reset('%s')" % template_id)

    # TODO reload_scripts

    activate(template_id)