File: properties_workspace.py

package info (click to toggle)
blender 4.3.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 309,564 kB
  • sloc: cpp: 2,385,210; python: 330,236; ansic: 280,972; xml: 2,446; sh: 972; javascript: 317; makefile: 170
file content (190 lines) | stat: -rw-r--r-- 6,585 bytes parent folder | download
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
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

import bpy
from bpy.types import (
    Panel,
    UIList,
)
from bpy.app.translations import pgettext_iface as iface_

from rna_prop_ui import PropertyPanel


class WorkSpaceButtonsPanel:
    # bl_space_type = 'PROPERTIES'
    # bl_region_type = 'WINDOW'
    # bl_context = ".workspace"

    # Developer note: this is displayed in tool settings as well as the 3D view.
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = "Tool"


class WORKSPACE_PT_main(WorkSpaceButtonsPanel, Panel):
    bl_label = "Workspace"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        workspace = context.workspace

        layout = self.layout
        layout.use_property_split = True
        layout.use_property_decorate = False

        layout.prop(workspace, "use_pin_scene")
        layout.prop(workspace, "object_mode", text="Mode")


class WORKSPACE_PT_addons(WorkSpaceButtonsPanel, Panel):
    bl_label = "Filter Add-ons"
    bl_parent_id = "WORKSPACE_PT_main"
    addon_map = {}
    owner_ids = set()

    def draw_header(self, context):
        workspace = context.workspace
        self.layout.prop(workspace, "use_filter_by_owner", text="")

    def draw(self, context):
        layout = self.layout

        workspace = context.workspace
        prefs = context.preferences

        import addon_utils
        WORKSPACE_PT_addons.addon_map = {mod.__name__: mod for mod in addon_utils.modules()}
        WORKSPACE_PT_addons.owner_ids = {owner_id.name for owner_id in workspace.owner_ids}
        known_addons = set()
        for addon in prefs.addons:
            if addon.module in WORKSPACE_PT_addons.owner_ids:
                known_addons.add(addon.module)
        unknown_addons = WORKSPACE_PT_addons.owner_ids.difference(known_addons)
        layout.template_list(
            "WORKSPACE_UL_addons_items",
            "",
            context.preferences,
            "addons",
            context.workspace,
            "active_addon",
            rows=8,
        )
        # Detect unused
        if unknown_addons:
            layout.label(text="Unknown add-ons", icon='ERROR')
            col = layout.box().column(align=True)
            for addon_module_name in sorted(unknown_addons):
                row = col.row()
                row.alignment = 'LEFT'
                row.operator(
                    "wm.owner_disable",
                    icon='CHECKBOX_HLT',
                    text=addon_module_name,
                    emboss=False,
                ).owner_id = addon_module_name


class WORKSPACE_UL_addons_items(UIList):

    @staticmethod
    def _ui_label_from_addon(addon):
        # Return: `Category: Addon Name` when the add-on is known, otherwise it's module name.
        import addon_utils
        module = WORKSPACE_PT_addons.addon_map.get(addon.module)
        if not module:
            return addon.module
        bl_info = addon_utils.module_bl_info(module)
        return "{:s}: {:s}".format(iface_(bl_info["category"]), iface_(bl_info["name"]))

    @staticmethod
    def _filter_addons_by_category_name(pattern, bitflag, addons, reverse=False):
        # Set FILTER_ITEM for addons which category and name matches filter_name one (case-insensitive).
        # pattern is the filtering pattern.
        # return a list of flags based on given bit flag, or an empty list if no pattern is given
        # or list addons is empty.

        if not pattern or not addons:  # Empty pattern or list = no filtering!
            return []

        import fnmatch
        import re

        # Implicitly add heading/trailing wildcards.
        pattern_regex = re.compile(fnmatch.translate("*" + pattern + "*"), re.IGNORECASE)

        flags = [0] * len(addons)

        for i, addon in enumerate(addons):
            name = WORKSPACE_UL_addons_items._ui_label_from_addon(addon)
            # This is similar to a logical XOR.
            if bool(name and pattern_regex.match(name)) is not reverse:
                flags[i] |= bitflag
        return flags

    @staticmethod
    def _sort_addons_by_category_name(addons):
        # Re-order addons using their categories and names (case-insensitive).
        # return a list mapping org_idx -> new_idx, or an empty list if no sorting has been done.
        _sort = [(idx, WORKSPACE_UL_addons_items._ui_label_from_addon(addon)) for idx, addon in enumerate(addons)]
        return bpy.types.UI_UL_list.sort_items_helper(_sort, lambda e: e[1].lower())

    def filter_items(self, _context, data, property):
        addons = getattr(data, property)
        flags = []
        indices = []

        # Filtering by category and name
        if self.filter_name:
            flags = self._filter_addons_by_category_name(
                self.filter_name, self.bitflag_filter_item, addons, reverse=self.use_filter_invert)
        if not flags:
            flags = [self.bitflag_filter_item] * len(addons)
        # Filer addons without registered modules
        for idx, addon in enumerate(addons):
            if not WORKSPACE_PT_addons.addon_map.get(addon.module):
                flags[idx] = 0
        if self.use_filter_sort_alpha:
            indices = self._sort_addons_by_category_name(addons)
        return flags, indices

    def draw_item(self, context, layout, _data, addon, icon, _active_data, _active_propname, _index):
        row = layout.row()
        row.active = context.workspace.use_filter_by_owner
        row.emboss = 'NONE'
        row.label(text=WORKSPACE_UL_addons_items._ui_label_from_addon(addon))
        row = row.row()
        row.alignment = 'RIGHT'
        is_enabled = addon.module in WORKSPACE_PT_addons.owner_ids
        row.operator(
            "wm.owner_disable" if is_enabled else "wm.owner_enable",
            icon='CHECKBOX_HLT' if is_enabled else 'CHECKBOX_DEHLT',
            text="",
        ).owner_id = addon.module


class WORKSPACE_PT_custom_props(WorkSpaceButtonsPanel, PropertyPanel, Panel):
    bl_parent_id = "WORKSPACE_PT_main"

    _context_path = "workspace"
    _property_type = bpy.types.WorkSpace


classes = (
    WORKSPACE_UL_addons_items,

    WORKSPACE_PT_main,
    WORKSPACE_PT_addons,
    WORKSPACE_PT_custom_props,
)


bpy.types.WorkSpace.active_addon = bpy.props.IntProperty(
    name="Active Add-on", description="Active Add-on in the Workspace Add-ons filter")


if __name__ == "__main__":  # only for live edit.
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)