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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
# ##### 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 compliant>
import bpy
from bpy.types import (
Menu,
)
__all__ = (
"ToolSelectPanelHelper",
)
class ToolSelectPanelHelper:
"""
Generic Class, can be used for any toolbar.
- keymap_prefix:
The text prefix for each key-map for this spaces tools.
- tools_all():
Returns all tools defined.
- tools_from_context(context):
Returns tools available in this context.
Each tool is a triplet:
``(tool_name, manipulator_group_idname, keymap_actions)``
For a separator in the toolbar, use ``None``.
Where:
``tool_name``
is the name to display in the interface.
``manipulator_group_idname``
is an optional manipulator group to activate when the tool is set.
``keymap_actions``
an optional triple of: ``(operator_id, operator_properties, keymap_item_args)``
"""
@staticmethod
def _tool_is_group(tool):
return type(tool[0]) is not str
@staticmethod
def _tools_flatten(tools):
for item in tools:
if item is not None:
if ToolSelectPanelHelper._tool_is_group(item):
for sub_item in item:
if sub_item is not None:
yield sub_item
else:
yield item
@classmethod
def _tool_vars_from_def(cls, item):
text, mp_idname, actions = item
km, km_idname = (None, None) if actions is None else cls._tool_keymap[text]
return (km_idname, mp_idname)
@staticmethod
def _tool_vars_from_active_with_index(context):
workspace = context.workspace
return (
(workspace.tool_keymap or None, workspace.tool_manipulator_group or None),
workspace.tool_index,
)
@staticmethod
def _tool_vars_from_button_with_index(context):
props = context.button_operator
return (
(props.keymap or None or None, props.manipulator_group or None),
props.index,
)
@classmethod
def _km_actionmouse_simple(cls, kc, text, actions):
# standalone
def props_assign_recursive(rna_props, py_props):
for prop_id, value in py_props.items():
if isinstance(value, dict):
props_assign_recursive(getattr(rna_props, prop_id), value)
else:
setattr(rna_props, prop_id, value)
km_idname = cls.keymap_prefix + text
km = kc.keymaps.get(km_idname)
if km is not None:
return km, km_idname
km = kc.keymaps.new(km_idname, space_type=cls.bl_space_type, region_type='WINDOW')
for op_idname, op_props_dict, kmi_kwargs in actions:
kmi = km.keymap_items.new(op_idname, **kmi_kwargs)
kmi_props = kmi.properties
if op_props_dict:
props_assign_recursive(kmi.properties, op_props_dict)
return km, km_idname
@classmethod
def register(cls):
wm = bpy.context.window_manager
# XXX, should we be manipulating the user-keyconfig on load?
# Perhaps this should only add when keymap items don't already exist.
#
# This needs some careful consideration.
kc = wm.keyconfigs.user
# {tool_name: (keymap, keymap_idname, manipulator_group_idname), ...}
cls._tool_keymap = {}
# Track which tool-group was last used for non-active groups.
# Blender stores the active tool-group index.
#
# {tool_name_first: index_in_group, ...}
cls._tool_group_active = {}
# ignore in background mode
if kc is None:
return
for item in ToolSelectPanelHelper._tools_flatten(cls.tools_all()):
text, mp_idname, actions = item
if actions is not None:
km, km_idname = cls._km_actionmouse_simple(kc, text, actions)
cls._tool_keymap[text] = km, km_idname
def draw(self, context):
# XXX, this UI isn't very nice.
# We might need to create new button types for this.
# Since we probably want:
# - tool-tips that include multiple key shortcuts.
# - ability to click and hold to expose sub-tools.
workspace = context.workspace
tool_def_active, index_active = self._tool_vars_from_active_with_index(context)
layout = self.layout
for tool_items in self.tools_from_context(context):
if tool_items:
col = layout.column(align=True)
for item in tool_items:
if item is None:
col = layout.column(align=True)
continue
if self._tool_is_group(item):
is_active = False
i = 0
for i, sub_item in enumerate(item):
if sub_item is None:
continue
tool_def = self._tool_vars_from_def(sub_item)
is_active = (tool_def == tool_def_active)
if is_active:
index = i
break
del i, sub_item
if is_active:
# not ideal, write this every time :S
self._tool_group_active[item[0][0]] = index
else:
index = self._tool_group_active.get(item[0][0], 0)
item = item[index]
use_menu = True
else:
index = -1
use_menu = False
tool_def = self._tool_vars_from_def(item)
is_active = (tool_def == tool_def_active)
if use_menu:
props = col.operator_menu_hold(
"wm.tool_set",
text=item[0],
depress=is_active,
menu="WM_MT_toolsystem_submenu",
)
else:
props = col.operator(
"wm.tool_set",
text=item[0],
depress=is_active,
)
props.keymap = tool_def[0] or ""
props.manipulator_group = tool_def[1] or ""
props.index = index
def tools_from_context(cls, context):
return (cls._tools[None], cls._tools.get(context.mode, ()))
# The purpose of this menu is to be a generic popup to select between tools
# in cases when a single tool allows to select alternative tools.
class WM_MT_toolsystem_submenu(Menu):
bl_label = ""
@staticmethod
def _tool_group_from_button(context):
# Lookup the tool definitions based on the space-type.
space_type = context.space_data.type
cls = next(
(cls for cls in ToolSelectPanelHelper.__subclasses__()
if cls.bl_space_type == space_type),
None
)
if cls is not None:
tool_def_button, index_button = cls._tool_vars_from_button_with_index(context)
for item_items in cls.tools_from_context(context):
for item_group in item_items:
if (item_group is not None) and ToolSelectPanelHelper._tool_is_group(item_group):
if index_button < len(item_group):
item = item_group[index_button]
tool_def = cls._tool_vars_from_def(item)
is_active = (tool_def == tool_def_button)
if is_active:
return cls, item_group, index_button
return None, None, -1
def draw(self, context):
layout = self.layout
cls, item_group, index_active = self._tool_group_from_button(context)
if item_group is None:
# Should never happen, just in case
layout.label("Unable to find toolbar group")
return
index = 0
for item in item_group:
if item is None:
layout.separator()
continue
tool_def = cls._tool_vars_from_def(item)
props = layout.operator(
"wm.tool_set",
text=item[0],
)
props.keymap = tool_def[0] or ""
props.manipulator_group = tool_def[1] or ""
props.index = index
index += 1
classes = (
WM_MT_toolsystem_submenu,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)
|