WM: Initial Tool System

The tool-system it's self is primitive and may be changed.

Adding to 2.8 to develop operators and manipulators as tools.

Currently this is exposed in the toolbar, collapsed by default.
Work-flow remains unchanged if you don't change the active tool.

Placing the 3D cursor is now a Click instead of a Press event,
this allows tweak events to be mapped to tools such as border select,
keeping click for 3D cursor placement when selection tools are set.
This commit is contained in:
Campbell Barton 2017-10-21 16:19:48 +11:00
parent b66728d63d
commit e1e7b6db2e
8 changed files with 383 additions and 2 deletions

View File

@ -64,6 +64,12 @@ _modules = [
"properties_scene",
"properties_texture",
"properties_world",
# Generic Space Modules
#
# Depends on DNA_WORKSPACE_TOOL (C define).
"space_toolsystem_toolbar",
"space_clip",
"space_console",
"space_dopesheet",

View File

@ -0,0 +1,127 @@
# ##### 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
__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)``
"""
@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.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 = {}
for t in cls.tools_all():
text, mp_idname, actions = t
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
km_idname_active = workspace.tool_keymap or None
mp_idname_active = workspace.tool_manipulator_group or None
layout = self.layout
for tool_items in self.tools_from_context(context):
if tool_items:
col = layout.box().column()
for item in tool_items:
if item is None:
col = layout.box().column()
continue
text, mp_idname, actions = item
if actions is not None:
km, km_idname = self._tool_keymap[text]
else:
km = None
km_idname = None
props = col.operator(
"wm.tool_set",
text=text,
emboss=(
km_idname_active == km_idname and
mp_idname_active == mp_idname
),
)
props.keymap = km_idname or ""
props.manipulator_group = mp_idname or ""

View File

@ -0,0 +1,134 @@
# ##### 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>
# For now group all tools together
# we may want to move these into per space-type files.
#
# For now keep this in a single file since it's an area that may change,
# so avoid making changes all over the place.
from bpy.types import Panel
from .space_toolsystem_common import (
ToolSelectPanelHelper,
)
class VIEW3D_PT_tools_active(ToolSelectPanelHelper, Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = "Tools"
bl_label = "Active Tool (Test)"
bl_options = {'DEFAULT_CLOSED'}
# Satisfy the 'ToolSelectPanelHelper' API.
keymap_prefix = "3D View Tool: "
@classmethod
def tools_from_context(cls, context):
return (cls._tools[None], cls._tools.get(context.mode, ()))
@classmethod
def tools_all(cls):
return [t for t_list in cls._tools.values() for t in t_list if t is not None]
# Internal Data
# for reuse
_tools_transform = (
("Translate", None,
(("transform.translate", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),)),
("Rotate", None,
(("transform.rotate", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),)),
("Scale", None,
(("transform.resize", dict(release_confirm=True), dict(type='EVT_TWEAK_A', value='ANY')),)),
)
_tools = {
None: [
("Cursor", None,
(("view3d.cursor3d", dict(), dict(type='ACTIONMOUSE', value='CLICK')),)),
("Select Border", None, (
("view3d.select_border", dict(deselect=False), dict(type='EVT_TWEAK_A', value='ANY')),
("view3d.select_border", dict(deselect=True), dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
)),
("Select Circle", None, (
("view3d.select_circle", dict(deselect=False), dict(type='ACTIONMOUSE', value='PRESS')),
("view3d.select_circle", dict(deselect=True), dict(type='ACTIONMOUSE', value='PRESS', ctrl=True)),
)),
("Select Lasso", None, (
("view3d.select_lasso",
dict(deselect=False), dict(type='EVT_TWEAK_A', value='ANY')),
("view3d.select_lasso",
dict(deselect=True), dict(type='EVT_TWEAK_A', value='ANY', ctrl=True)),
)),
],
'OBJECT': [
*_tools_transform,
],
'POSE': [
*_tools_transform,
],
'EDIT_ARMATURE': [
*_tools_transform,
("Roll", None, (
("transform.transform",
dict(release_confirm=True, mode='BONE_ROLL'),
dict(type='EVT_TWEAK_A', value='ANY')),
)),
None,
("Extrude Cursor", None,
(("armature.click_extrude", dict(), dict(type='ACTIONMOUSE', value='PRESS')),)),
],
'EDIT_MESH': [
*_tools_transform,
None,
("Rip Region", None, (
("mesh.rip_move", dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
dict(type='ACTIONMOUSE', value='PRESS')),
)),
("Rip Edge", None, (
("mesh.rip_edge_move", dict(TRANSFORM_OT_translate=dict(release_confirm=True)),
dict(type='ACTIONMOUSE', value='PRESS')),
)),
("Knife", None, (("mesh.knife_tool", dict(wait_for_input=False), dict(type='ACTIONMOUSE', value='PRESS')),)),
("Bisect", None, (("mesh.bisect", dict(), dict(type='EVT_TWEAK_A', value='ANY')),)),
("Extrude Cursor", None,
(("mesh.dupli_extrude_cursor", dict(), dict(type='ACTIONMOUSE', value='PRESS')),)),
],
'EDIT_CURVE': [
*_tools_transform,
None,
("Draw", None,
(("curve.draw", dict(wait_for_input=False), dict(type='ACTIONMOUSE', value='PRESS')),)),
("Extrude Cursor", None,
(("curve.vertex_add", dict(), dict(type='ACTIONMOUSE', value='PRESS')),)),
],
}
classes = (
VIEW3D_PT_tools_active,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@ -238,7 +238,7 @@ void view3d_keymap(wmKeyConfig *keyconf)
/* only for region 3D window */
keymap = WM_keymap_find(keyconf, "3D View", SPACE_VIEW3D, 0);
WM_keymap_verify_item(keymap, "VIEW3D_OT_cursor3d", ACTIONMOUSE, KM_PRESS, 0, 0);
WM_keymap_verify_item(keymap, "VIEW3D_OT_cursor3d", ACTIONMOUSE, KM_CLICK, 0, 0);
WM_keymap_verify_item(keymap, "VIEW3D_OT_rotate", MIDDLEMOUSE, KM_PRESS, 0, 0);
WM_keymap_verify_item(keymap, "VIEW3D_OT_move", MIDDLEMOUSE, KM_PRESS, KM_SHIFT, 0);

View File

@ -50,6 +50,16 @@
# endif
#endif
/* Currently testing, allow to disable. */
#define USE_WORKSPACE_TOOL
typedef struct bToolDef {
/* either the keymap AND/OR manipulator_group must be defined. */
char keymap[64];
char manipulator_group[64];
int spacetype;
int _pad;
} bToolDef;
/**
* \brief Wrapper for bScreen.
@ -80,6 +90,9 @@ typedef struct WorkSpace {
int object_mode DNA_PRIVATE_WORKSPACE; /* enum eObjectMode */
int flags DNA_PRIVATE_WORKSPACE; /* enum eWorkSpaceFlags */
/* should be: '#ifdef USE_WORKSPACE_TOOL'. */
bToolDef tool;
struct SceneLayer *render_layer DNA_PRIVATE_WORKSPACE;
char engine_id[32]; /* Render Engine. */

View File

@ -146,6 +146,16 @@ static void rna_def_workspace(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Mode", "Object interaction mode");
#endif
prop = RNA_def_property(srna, "tool_keymap", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "tool.keymap");
RNA_def_property_ui_text(prop, "Active Tool", "Currently active tool keymap");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "tool_manipulator_group", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, NULL, "tool.manipulator_group");
RNA_def_property_ui_text(prop, "Active Tool", "Currently active tool manipulator");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
prop = RNA_def_property(srna, "orientations", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "transform_orientations", NULL);
RNA_def_property_struct_type(prop, "TransformOrientation");

View File

@ -2722,9 +2722,42 @@ void wm_event_do_handlers(bContext *C)
wm_drags_check_ops(C, event);
}
}
#ifdef USE_WORKSPACE_TOOL
/* How to solve properly?
*
* Handlers are stored in each region,
* however the tool-system swaps keymaps often and isn't stored
* per region.
*
* Need to investigate how this could be done better.
* We might need to add a more dynamic handler type that uses a callback
* to fetch its current keymap.
*/
wmEventHandler sneaky_handler = {NULL};
if (ar->regiontype == RGN_TYPE_WINDOW) {
WorkSpace *workspace = WM_window_get_active_workspace(win);
if (workspace->tool.keymap[0] &&
workspace->tool.spacetype == sa->spacetype)
{
wmKeyMap *km = WM_keymap_find_all(
C, workspace->tool.keymap, sa->spacetype, RGN_TYPE_WINDOW);
if (km != NULL) {
sneaky_handler.keymap = km;
BLI_addhead(&ar->handlers, &sneaky_handler);
}
}
}
#endif /* USE_WORKSPACE_TOOL */
action |= wm_handlers_do(C, event, &ar->handlers);
#ifdef USE_WORKSPACE_TOOL
if (sneaky_handler.keymap) {
BLI_remlink(&ar->handlers, &sneaky_handler);
}
#endif /* USE_WORKSPACE_TOOL */
/* fileread case (python), [#29489] */
if (CTX_wm_window(C) == NULL)
return;

View File

@ -52,6 +52,7 @@
#include "DNA_scene_types.h"
#include "DNA_userdef_types.h"
#include "DNA_windowmanager_types.h"
#include "DNA_workspace_types.h"
#include "BLT_translation.h"
@ -1743,6 +1744,60 @@ static void WM_OT_operator_defaults(wmOperatorType *ot)
ot->flag = OPTYPE_INTERNAL;
}
#ifdef USE_WORKSPACE_TOOL
/* ***************** Set Active Tool ************************* */
/* Developers note: in it's current form this doesn't need to be an operator,
* keep this as-is for now since it may end up setting an active key-map.
*/
static int wm_operator_tool_set_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
WorkSpace *workspace = CTX_wm_workspace(C);
ScrArea *sa = CTX_wm_area(C);
char id_keymap[sizeof(workspace->tool.keymap)];
char id_manipulator_group[sizeof(workspace->tool.manipulator_group)];
RNA_string_get(op->ptr, "keymap", id_keymap);
RNA_string_get(op->ptr, "manipulator_group", id_manipulator_group);
if (workspace->tool.manipulator_group[0]) {
wmManipulatorGroupType *wgt = WM_manipulatorgrouptype_find(workspace->tool.manipulator_group, false);
if (wgt != NULL) {
wmManipulatorMapType *mmap_type = WM_manipulatormaptype_ensure(&wgt->mmap_params);
WM_manipulatormaptype_group_unlink(C, bmain, mmap_type, wgt);
}
}
/* NOTE: we may want to move this logic into a function. */
{
BLI_strncpy(workspace->tool.keymap, id_keymap, sizeof(workspace->tool.keymap));
BLI_strncpy(workspace->tool.manipulator_group, id_manipulator_group, sizeof(workspace->tool.manipulator_group));
workspace->tool.spacetype = sa->spacetype;
}
if (workspace->tool.manipulator_group[0]) {
WM_manipulator_group_type_ensure(workspace->tool.manipulator_group);
}
return OPERATOR_FINISHED;
}
static void WM_OT_tool_set(wmOperatorType *ot)
{
ot->name = "Set Active Tool";
ot->idname = "WM_OT_tool_set";
ot->description = "Set the active tool";
ot->exec = wm_operator_tool_set_exec;
ot->flag = OPTYPE_INTERNAL;
RNA_def_string(ot->srna, "keymap", NULL, KMAP_MAX_NAME, "Key Map", "");
RNA_def_string(ot->srna, "manipulator_group", NULL, MAX_NAME, "Manipulator Group", "");
}
#endif /* USE_WORKSPACE_TOOL */
/* ***************** Splash Screen ************************* */
static void wm_block_splash_close(bContext *C, void *arg_block, void *UNUSED(arg))
@ -3604,6 +3659,9 @@ void wm_operatortype_init(void)
WM_operatortype_append(WM_OT_memory_statistics);
WM_operatortype_append(WM_OT_debug_menu);
WM_operatortype_append(WM_OT_operator_defaults);
#ifdef USE_WORKSPACE_TOOL
WM_operatortype_append(WM_OT_tool_set);
#endif
WM_operatortype_append(WM_OT_splash);
WM_operatortype_append(WM_OT_search_menu);
WM_operatortype_append(WM_OT_call_menu);