2017-10-21 05:19:48 +00:00
|
|
|
# ##### 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
|
2017-11-02 12:05:13 +00:00
|
|
|
from bpy.types import (
|
|
|
|
Menu,
|
|
|
|
)
|
2017-10-21 05:19:48 +00:00
|
|
|
|
|
|
|
__all__ = (
|
|
|
|
"ToolSelectPanelHelper",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2018-04-24 07:19:28 +00:00
|
|
|
# (filename -> icon_value) map
|
|
|
|
_icon_cache = {}
|
|
|
|
|
2017-10-21 05:19:48 +00:00
|
|
|
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():
|
2018-04-26 12:43:32 +00:00
|
|
|
Returns (context_mode, tools) tuple pair for all tools defined.
|
2017-10-21 05:19:48 +00:00
|
|
|
- tools_from_context(context):
|
|
|
|
Returns tools available in this context.
|
|
|
|
|
2018-04-26 05:39:15 +00:00
|
|
|
Each tool is a dict:
|
|
|
|
``(text=tool_name, icon=icon_name, widget=manipulator_group_idname, keymap=keymap_actions)``
|
2017-10-21 05:19:48 +00:00
|
|
|
For a separator in the toolbar, use ``None``.
|
|
|
|
|
|
|
|
Where:
|
|
|
|
``tool_name``
|
|
|
|
is the name to display in the interface.
|
2018-04-26 05:39:15 +00:00
|
|
|
``icon_name``
|
|
|
|
is the name of the icon to use (found in ``release/datafiles/icons``).
|
2017-10-21 05:19:48 +00:00
|
|
|
``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)``
|
|
|
|
"""
|
|
|
|
|
2018-04-24 07:19:28 +00:00
|
|
|
@staticmethod
|
|
|
|
def _icon_value_from_icon_handle(icon_name):
|
|
|
|
import os
|
|
|
|
if icon_name is not None:
|
|
|
|
assert(type(icon_name) is str)
|
|
|
|
icon_value = _icon_cache.get(icon_name)
|
|
|
|
if icon_value is None:
|
2018-04-25 12:05:48 +00:00
|
|
|
dirname = bpy.utils.resource_path('LOCAL')
|
2018-04-24 08:45:57 +00:00
|
|
|
if not dirname:
|
|
|
|
# TODO(campbell): use a better way of finding datafiles.
|
2018-04-25 12:05:48 +00:00
|
|
|
dirname = bpy.utils.resource_path('SYSTEM')
|
2018-04-24 07:19:28 +00:00
|
|
|
filename = os.path.join(dirname, "datafiles", "icons", icon_name + ".dat")
|
|
|
|
try:
|
|
|
|
icon_value = bpy.app.icons.new_triangles_from_file(filename)
|
|
|
|
except Exception as ex:
|
|
|
|
if os.path.exists(filename):
|
|
|
|
print("Missing icons:", filename, ex)
|
|
|
|
else:
|
|
|
|
print("Corrupt icon:", filename, ex)
|
|
|
|
icon_value = 0
|
|
|
|
_icon_cache[icon_name] = icon_value
|
|
|
|
return icon_value
|
|
|
|
else:
|
|
|
|
return 0
|
|
|
|
|
2017-11-02 12:05:13 +00:00
|
|
|
@staticmethod
|
|
|
|
def _tool_is_group(tool):
|
2018-04-26 05:31:39 +00:00
|
|
|
return type(tool) is not dict
|
2017-11-02 12:05:13 +00:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _tools_flatten(tools):
|
|
|
|
for item in tools:
|
2017-11-03 05:30:51 +00:00
|
|
|
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
|
2018-04-26 06:04:50 +00:00
|
|
|
def _tool_vars_from_def(cls, item, context_mode):
|
2018-04-26 05:39:15 +00:00
|
|
|
# For now be strict about whats in this dict
|
|
|
|
# prevent accidental adding unknown keys.
|
|
|
|
assert(len(item) == 4)
|
2018-04-26 05:31:39 +00:00
|
|
|
text = item["text"]
|
|
|
|
icon_name = item["icon"]
|
|
|
|
mp_idname = item["widget"]
|
|
|
|
actions = item["keymap"]
|
2018-04-26 06:04:50 +00:00
|
|
|
if actions is None:
|
|
|
|
km, km_idname = (None, None)
|
|
|
|
else:
|
|
|
|
km_test = cls._tool_keymap.get((context_mode, text))
|
|
|
|
if km_test is None and context_mode is not None:
|
|
|
|
km_test = cls._tool_keymap[None, text]
|
|
|
|
km, km_idname = km_test
|
2018-04-24 07:19:28 +00:00
|
|
|
return (km_idname, mp_idname), icon_name
|
2017-11-03 05:30:51 +00:00
|
|
|
|
|
|
|
@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,
|
|
|
|
)
|
2017-11-02 12:05:13 +00:00
|
|
|
|
2017-10-21 05:19:48 +00:00
|
|
|
@classmethod
|
2018-04-26 06:04:50 +00:00
|
|
|
def _km_action_simple(cls, kc, context_mode, text, actions):
|
2017-10-21 05:19:48 +00:00
|
|
|
|
|
|
|
# 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)
|
|
|
|
|
2018-04-26 06:10:52 +00:00
|
|
|
if context_mode is None:
|
|
|
|
context_mode = "All"
|
|
|
|
km_idname = f"{cls.keymap_prefix} {context_mode}, {text}"
|
2017-11-04 14:38:51 +00:00
|
|
|
km = kc.keymaps.get(km_idname)
|
|
|
|
if km is not None:
|
|
|
|
return km, km_idname
|
2017-10-21 05:19:48 +00:00
|
|
|
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
|
|
|
|
|
2018-04-26 06:10:52 +00:00
|
|
|
# {context_mode: {tool_name: (keymap, keymap_idname, manipulator_group_idname), ...}, ...}
|
2017-10-21 05:19:48 +00:00
|
|
|
cls._tool_keymap = {}
|
|
|
|
|
2017-11-03 05:30:51 +00:00
|
|
|
# Track which tool-group was last used for non-active groups.
|
|
|
|
# Blender stores the active tool-group index.
|
|
|
|
#
|
2017-11-02 12:30:01 +00:00
|
|
|
# {tool_name_first: index_in_group, ...}
|
|
|
|
cls._tool_group_active = {}
|
|
|
|
|
2017-10-26 11:04:48 +00:00
|
|
|
# ignore in background mode
|
|
|
|
if kc is None:
|
|
|
|
return
|
|
|
|
|
2018-04-26 12:43:32 +00:00
|
|
|
for context_mode, tools in cls.tools_all():
|
2018-04-26 06:04:50 +00:00
|
|
|
for item_parent in tools:
|
|
|
|
if item_parent is None:
|
|
|
|
continue
|
|
|
|
for item in item_parent if (cls._tool_is_group(item_parent)) else (item_parent,):
|
|
|
|
if item is None:
|
|
|
|
continue
|
|
|
|
actions = item["keymap"]
|
|
|
|
if actions is not None:
|
|
|
|
text = item["text"]
|
|
|
|
icon_name = item["icon"]
|
|
|
|
km, km_idname = cls._km_action_simple(kc, context_mode, text, actions)
|
|
|
|
cls._tool_keymap[context_mode, text] = km, km_idname
|
2017-10-21 05:19:48 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2018-04-26 06:04:50 +00:00
|
|
|
context_mode = context.mode
|
2017-11-03 05:30:51 +00:00
|
|
|
tool_def_active, index_active = self._tool_vars_from_active_with_index(context)
|
2017-10-21 05:19:48 +00:00
|
|
|
layout = self.layout
|
|
|
|
|
2018-04-24 07:19:28 +00:00
|
|
|
scale_y = 2.0
|
|
|
|
|
2018-04-24 14:33:38 +00:00
|
|
|
# TODO(campbell): expose ui_scale.
|
|
|
|
view2d = context.region.view2d
|
|
|
|
ui_scale = (
|
|
|
|
view2d.region_to_view(1.0, 0.0)[0] -
|
|
|
|
view2d.region_to_view(0.0, 0.0)[0]
|
|
|
|
)
|
|
|
|
show_text = (context.region.width / ui_scale) > 100.0
|
|
|
|
del view2d, ui_scale
|
|
|
|
|
2017-10-21 05:19:48 +00:00
|
|
|
for tool_items in self.tools_from_context(context):
|
|
|
|
if tool_items:
|
2017-11-02 04:52:16 +00:00
|
|
|
col = layout.column(align=True)
|
2018-04-24 07:19:28 +00:00
|
|
|
col.scale_y = scale_y
|
2017-10-21 05:19:48 +00:00
|
|
|
for item in tool_items:
|
|
|
|
if item is None:
|
2017-11-02 04:52:16 +00:00
|
|
|
col = layout.column(align=True)
|
2018-04-24 07:19:28 +00:00
|
|
|
col.scale_y = scale_y
|
2017-10-21 05:19:48 +00:00
|
|
|
continue
|
|
|
|
|
2017-11-02 12:05:13 +00:00
|
|
|
if self._tool_is_group(item):
|
|
|
|
is_active = False
|
2017-11-02 12:30:01 +00:00
|
|
|
i = 0
|
2017-11-03 05:30:51 +00:00
|
|
|
for i, sub_item in enumerate(item):
|
|
|
|
if sub_item is None:
|
2017-11-02 12:30:01 +00:00
|
|
|
continue
|
2018-04-26 06:04:50 +00:00
|
|
|
tool_def, icon_name = self._tool_vars_from_def(sub_item, context_mode)
|
2017-11-03 05:30:51 +00:00
|
|
|
is_active = (tool_def == tool_def_active)
|
2017-11-02 12:05:13 +00:00
|
|
|
if is_active:
|
|
|
|
index = i
|
|
|
|
break
|
|
|
|
del i, sub_item
|
2017-11-02 12:30:01 +00:00
|
|
|
|
|
|
|
if is_active:
|
|
|
|
# not ideal, write this every time :S
|
2018-04-26 05:31:39 +00:00
|
|
|
self._tool_group_active[item[0]["text"]] = index
|
2017-11-02 12:30:01 +00:00
|
|
|
else:
|
2018-04-26 05:31:39 +00:00
|
|
|
index = self._tool_group_active.get(item[0]["text"], 0)
|
2017-11-02 12:30:01 +00:00
|
|
|
|
2017-11-02 12:05:13 +00:00
|
|
|
item = item[index]
|
|
|
|
use_menu = True
|
2017-10-21 05:19:48 +00:00
|
|
|
else:
|
2017-11-02 12:05:13 +00:00
|
|
|
index = -1
|
|
|
|
use_menu = False
|
|
|
|
|
2018-04-26 06:04:50 +00:00
|
|
|
tool_def, icon_name = self._tool_vars_from_def(item, context_mode)
|
2017-11-03 05:30:51 +00:00
|
|
|
is_active = (tool_def == tool_def_active)
|
2018-04-24 07:19:28 +00:00
|
|
|
icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(icon_name)
|
2017-11-02 12:05:13 +00:00
|
|
|
if use_menu:
|
|
|
|
props = col.operator_menu_hold(
|
|
|
|
"wm.tool_set",
|
2018-04-26 06:18:41 +00:00
|
|
|
text=item["text"] if show_text else "",
|
2017-11-02 12:05:13 +00:00
|
|
|
depress=is_active,
|
|
|
|
menu="WM_MT_toolsystem_submenu",
|
2018-04-24 07:19:28 +00:00
|
|
|
icon_value=icon_value,
|
2017-11-02 12:05:13 +00:00
|
|
|
)
|
|
|
|
else:
|
|
|
|
props = col.operator(
|
|
|
|
"wm.tool_set",
|
2018-04-26 06:18:41 +00:00
|
|
|
text=item["text"] if show_text else "",
|
2017-11-02 12:05:13 +00:00
|
|
|
depress=is_active,
|
2018-04-24 07:19:28 +00:00
|
|
|
icon_value=icon_value,
|
2017-11-02 12:05:13 +00:00
|
|
|
)
|
|
|
|
|
2017-11-03 05:30:51 +00:00
|
|
|
props.keymap = tool_def[0] or ""
|
|
|
|
props.manipulator_group = tool_def[1] or ""
|
2017-11-02 12:05:13 +00:00
|
|
|
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):
|
2018-04-26 06:04:50 +00:00
|
|
|
context_mode = context.mode
|
2017-11-02 12:05:13 +00:00
|
|
|
# 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:
|
2017-11-03 05:30:51 +00:00
|
|
|
tool_def_button, index_button = cls._tool_vars_from_button_with_index(context)
|
2017-11-02 12:05:13 +00:00
|
|
|
|
|
|
|
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]
|
2018-04-26 06:04:50 +00:00
|
|
|
tool_def, icon_name = cls._tool_vars_from_def(item, context_mode)
|
2017-11-03 05:30:51 +00:00
|
|
|
is_active = (tool_def == tool_def_button)
|
2017-11-02 12:05:13 +00:00
|
|
|
if is_active:
|
|
|
|
return cls, item_group, index_button
|
|
|
|
return None, None, -1
|
|
|
|
|
|
|
|
def draw(self, context):
|
2018-04-26 06:04:50 +00:00
|
|
|
context_mode = context.mode
|
2017-11-02 12:05:13 +00:00
|
|
|
layout = self.layout
|
2018-04-24 07:19:28 +00:00
|
|
|
layout.scale_y = 2.0
|
|
|
|
|
2017-11-02 12:05:13 +00:00
|
|
|
cls, item_group, index_active = self._tool_group_from_button(context)
|
|
|
|
if item_group is None:
|
|
|
|
# Should never happen, just in case
|
2017-11-03 05:30:51 +00:00
|
|
|
layout.label("Unable to find toolbar group")
|
2017-11-02 12:05:13 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
index = 0
|
|
|
|
for item in item_group:
|
|
|
|
if item is None:
|
|
|
|
layout.separator()
|
|
|
|
continue
|
2018-04-26 06:04:50 +00:00
|
|
|
tool_def, icon_name = cls._tool_vars_from_def(item, context_mode)
|
2018-04-24 07:19:28 +00:00
|
|
|
icon_value = ToolSelectPanelHelper._icon_value_from_icon_handle(icon_name)
|
2017-11-02 12:05:13 +00:00
|
|
|
props = layout.operator(
|
|
|
|
"wm.tool_set",
|
2018-04-26 05:31:39 +00:00
|
|
|
text=item["text"],
|
2018-04-24 07:19:28 +00:00
|
|
|
icon_value=icon_value,
|
2017-11-02 12:05:13 +00:00
|
|
|
)
|
2017-11-03 05:30:51 +00:00
|
|
|
props.keymap = tool_def[0] or ""
|
|
|
|
props.manipulator_group = tool_def[1] or ""
|
2017-11-02 12:05:13 +00:00
|
|
|
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)
|