Driver Setup Workflow Improvement: Property Eyedropper

This commit brings some long requested improvements to the workflow for setting up
drivers, which should make it easier and faster to set up new drivers in a more
interactive fashion.

The new workflow is as follows:
1) Hover over the property (e.g. "Lamp Energy" or "Y Location") or properties ("Rotation")
   you wish to add drivers to. We'll refer to this as the "destination"
2) Ctrl-D to active the new "Add Drivers" eyedropper
3) Click on the property you want to use as the source/target. The property under the
   mouse will be used to drive the property you invoked Ctrl-D on.

For example, to drive the X, Y, and Z location of the Cube using the Y Location of the Lamp,
hover over any of the X/Y/Z location buttons, hit Ctrl-D, then click on the Y-Location
button of the Lamp object. Drivers will be added to the X, Y, and Z Location properties
of the Cube; each driver will have a single variable, which uses the Y-Location Transform
Channel of the Lamp.


Tips:
- Transform properties will automatically create "Transform Channel" driver variables.
  Everything else will use "Single Property" ones

- Due to the way that Blender's UI Context works, you'll need two Properties Panel instances
  open (and to have pinned one of the two to show the properties for the unselected
  object). It's slightly clunky, but necessary for implementing a workflow like this,
  as the UI cannot be manipulated while using eyedroppers to pick data.

- The eyedropper operator implemented here actually has three modes of operation.
  1) The "1-N" (one to many) mode is the default used for Ctrl-D, and "Add Driver to All"
     in the RMB Menu. This is the behaviour described above.
  2) There's also a "1-1" (one to one) mode that is used for the "Add Single Driver" in the
     RMB Menu.
  3) Finally, there's the "N-N" mode (many to many), which isn't currently exposed.
     The point of this is to allow mapping XYZ to XYZ elementwise (i.e. direct copying)
     which is useful for things like locations, rotations, scaling, and colours.


Implementation Notes:
- The bulk of the driver adding logic is in editors/animation/drivers.c, where most of
  the Driver UI operators and tools are defined

- The property eyedropper code is in interface_eyedropper.c along with all the other
  eyedroppers (even though they don't share much actual code in common). However, this
  turns out to be necessary, as we can't get access to many of the low-level buttons API's
  otherwise.

Todo:
- It may be necessary to restore a way to access the old behaviour (i.e. "manual setup")
  in case it is not practical to immediately pick a property.

- Other things to investigate here include extra hotkeys (e.g. Ctrl-Shift-D for Add Single?),
  and to expose the N-N mode.

- Other things we could try include interactively applying scaling factors, picking
  multiple targets (e.g. for location difference and rotation difference drivers),
  and/or other ways of using these property picking methods.
This commit is contained in:
Joshua Leung 2016-03-26 17:55:42 +13:00
parent ee9898e0fa
commit 0512e20ae9
6 changed files with 440 additions and 23 deletions

@ -37,6 +37,7 @@
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BLI_string.h"
#include "DNA_anim_types.h"
#include "DNA_texture_types.h"
@ -154,6 +155,198 @@ FCurve *verify_driver_fcurve(ID *id, const char rna_path[], const int array_inde
/* ************************************************** */
/* Driver Management API */
/* Mapping Types enum for operators */
// XXX: These names need reviewing
EnumPropertyItem prop_driver_create_mapping_types[] = {
{CREATEDRIVER_MAPPING_1_N, "SINGLE_MANY", 0, "Single Target, Multiple Properties",
"Use the picked item to drive all components of this property"},
{CREATEDRIVER_MAPPING_1_1, "DIRECT", 0, "Single Item Only",
"Use picked item to drive property under mouse"},
{CREATEDRIVER_MAPPING_N_N, "MATCH", 0, "Match Indices",
"Create drivers for each pair of corresponding elements"},
{0, NULL, 0, NULL, NULL}
};
/* --------------------------------- */
/* Helper for ANIM_add_driver_with_target - Adds the actual driver */
static int add_driver_with_target(
ReportList *reports,
ID *dst_id, const char dst_path[], int dst_index,
ID *src_id, const char src_path[], int src_index,
PointerRNA *src_ptr, PropertyRNA *src_prop,
short flag, int driver_type)
{
FCurve *fcu;
short add_mode = (flag & CREATEDRIVER_WITH_FMODIFIER) ? 2 : 1;
const char *prop_name = RNA_property_identifier(src_prop);
/* Create F-Curve with Driver */
fcu = verify_driver_fcurve(dst_id, dst_path, dst_index, add_mode);
if (fcu && fcu->driver) {
ChannelDriver *driver = fcu->driver;
DriverVar *dvar;
/* Set the type of the driver */
driver->type = driver_type;
BLI_strncpy(driver->expression, "var", sizeof(driver->expression)); /* XXX: if we have N-1 mapping, we need to include all those here... */
/* Create a driver variable for the target
* - For transform properties, we want to automatically use "transform channel" instead
* (The only issue is with quat rotations vs euler channels...)
*/
dvar = driver_add_new_variable(driver);
if (ELEM(src_ptr->type, &RNA_Object, &RNA_PoseBone) &&
(STREQ(prop_name, "location") || STREQ(prop_name, "scale") || strstr(prop_name, "rotation_")))
{
/* Transform Channel */
DriverTarget *dtar;
driver_change_variable_type(dvar, DVAR_TYPE_TRANSFORM_CHAN);
dtar = &dvar->targets[0];
/* Bone or Object target? */
dtar->id = src_id;
dtar->idtype = GS(src_id->name);
if (src_ptr->type == &RNA_PoseBone) {
RNA_string_get(src_ptr, "name", dtar->pchan_name);
}
/* Transform channel depends on type */
if (STREQ(prop_name, "location")) {
if (src_index == 2)
dtar->transChan = DTAR_TRANSCHAN_LOCZ;
else if (src_index == 1)
dtar->transChan = DTAR_TRANSCHAN_LOCY;
else
dtar->transChan = DTAR_TRANSCHAN_LOCX;
}
else if (STREQ(prop_name, "scale")) {
if (src_index == 2)
dtar->transChan = DTAR_TRANSCHAN_SCALEZ;
else if (src_index == 1)
dtar->transChan = DTAR_TRANSCHAN_SCALEY;
else
dtar->transChan = DTAR_TRANSCHAN_SCALEX;
}
else {
/* XXX: With quaternions and axis-angle, this mapping might not be correct...
* But since those have 4 elements instead, there's not much we can do
*/
if (src_index == 2)
dtar->transChan = DTAR_TRANSCHAN_ROTZ;
else if (src_index == 1)
dtar->transChan = DTAR_TRANSCHAN_ROTY;
else
dtar->transChan = DTAR_TRANSCHAN_ROTX;
}
}
else {
/* Single RNA Property */
DriverTarget *dtar = &dvar->targets[0];
/* ID is as-is */
dtar->id = src_id;
dtar->idtype = GS(src_id->name);
/* Need to make a copy of the path (or build one with array index built in) */
if (RNA_property_array_check(src_prop)) {
dtar->rna_path = BLI_sprintfN("%s[%d]", src_path, src_index);
}
else {
dtar->rna_path = BLI_strdup(src_path);
}
}
}
/* set the done status */
return (fcu != NULL);
}
/* Main Driver Management API calls:
* Add a new driver for the specified property on the given ID block,
* and make it be driven by the specified target.
*
* This is intended to be used in conjunction with a modal "eyedropper"
* for picking the variable that is going to be used to drive this one.
*
* - flag: eCreateDriverFlags
* - driver_type: eDriver_Types
* - mapping_type: eCreateDriver_MappingTypes
*/
int ANIM_add_driver_with_target(
ReportList *reports,
ID *dst_id, const char dst_path[], int dst_index,
ID *src_id, const char src_path[], int src_index,
short flag, int driver_type, short mapping_type)
{
PointerRNA id_ptr, ptr;
PropertyRNA *prop;
PointerRNA id_ptr2, ptr2;
PropertyRNA *prop2;
int done_tot = 0;
/* validate pointers first - exit if failure */
RNA_id_pointer_create(dst_id, &id_ptr);
if (RNA_path_resolve_property(&id_ptr, dst_path, &ptr, &prop) == false) {
BKE_reportf(reports, RPT_ERROR,
"Could not add driver, as RNA path is invalid for the given ID (ID = %s, path = %s)",
dst_id->name, dst_path);
return 0;
}
RNA_id_pointer_create(src_id, &id_ptr2);
if (RNA_path_resolve_property(&id_ptr2, src_path, &ptr2, &prop2) == false) {
/* No target - So, fall back to default method for adding a "simple" driver normally */
return ANIM_add_driver(reports, dst_id, dst_path, dst_index, flag, driver_type);
}
/* handle curve-property mappings based on mapping_type */
switch (mapping_type) {
case CREATEDRIVER_MAPPING_N_N: /* N-N - Try to match as much as possible, then use the first one */
{
/* Use the shorter of the two (to avoid out of bounds access) */
int dst_len = (RNA_property_array_check(prop)) ? RNA_property_array_length(&ptr, prop) : 1;
int src_len = (RNA_property_array_check(prop)) ? RNA_property_array_length(&ptr2, prop2) : 1;
int len = MIN2(dst_len, src_len);
int i;
for (i = 0; i < len; i++) {
done_tot += add_driver_with_target(reports, dst_id, dst_path, i, src_id, src_path, i, &ptr2, prop2, flag, driver_type);
}
break;
}
case CREATEDRIVER_MAPPING_1_N: /* 1-N - Specified target index for all */
default:
{
int len = (RNA_property_array_check(prop)) ? RNA_property_array_length(&ptr, prop) : 1;
int i;
for (i = 0; i < len; i++) {
done_tot += add_driver_with_target(reports, dst_id, dst_path, i, src_id, src_path, src_index, &ptr2, prop2, flag, driver_type);
}
break;
}
case CREATEDRIVER_MAPPING_1_1: /* 1-1 - Use the specified index (unless -1) */
{
done_tot = add_driver_with_target(reports, dst_id, dst_path, dst_index, src_id, src_path, src_index, &ptr2, prop2, flag, driver_type);
break;
}
}
/* done */
return done_tot;
}
/* --------------------------------- */
/* Main Driver Management API calls:
* Add a new driver for the specified property on the given ID block
*/
@ -427,35 +620,31 @@ static int add_driver_button_exec(bContext *C, wmOperator *op)
{
PointerRNA ptr = {{NULL}};
PropertyRNA *prop = NULL;
int success = 0;
int index;
const bool all = RNA_boolean_get(op->ptr, "all");
int ret = OPERATOR_CANCELLED;
/* try to create driver using property retrieved from UI */
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
if (all)
index = -1;
if (ptr.id.data && ptr.data && prop && RNA_property_animateable(&ptr, prop)) {
char *path = BKE_animdata_driver_path_hack(C, &ptr, prop, NULL);
short flags = CREATEDRIVER_WITH_DEFAULT_DVAR;
wmOperatorType *ot = WM_operatortype_find("UI_OT_eyedropper_driver", true);
PointerRNA op_ptr;
if (path) {
success += ANIM_add_driver(op->reports, ptr.id.data, path, index, flags, DRIVER_TYPE_PYTHON);
WM_operator_properties_create_ptr(&op_ptr, ot);
MEM_freeN(path);
}
if (all)
RNA_enum_set(&op_ptr, "mapping_type", CREATEDRIVER_MAPPING_1_N);
else
RNA_enum_set(&op_ptr, "mapping_type", CREATEDRIVER_MAPPING_1_1);
ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_ptr);
WM_operator_properties_free(&op_ptr);
}
if (success) {
/* send updates */
UI_context_update_anim_flag(C);
DAG_relations_tag_update(CTX_data_main(C));
WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); // XXX
}
return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED;
return ret;
}
void ANIM_OT_driver_button_add(wmOperatorType *ot)

@ -235,6 +235,16 @@ typedef enum eCreateDriverFlags {
CREATEDRIVER_WITH_FMODIFIER = (1 << 1), /* create drivers with Generator FModifier (for backwards compat) */
} eCreateDriverFlags;
/* Heuristic to use for connecting target properties to driven ones */
typedef enum eCreateDriver_MappingTypes {
CREATEDRIVER_MAPPING_1_N = 0, /* 1 to Many - Use the specified index, and drive all elements with it */
CREATEDRIVER_MAPPING_1_1 = 1, /* 1 to 1 - Only for the specified index on each side */
CREATEDRIVER_MAPPING_N_N = 2, /* Many to Many - Match up the indices one by one (only for drivers on vectors/arrays) */
} eCreateDriver_MappingTypes;
/* RNA Enum of eCreateDriver_MappingTypes, for use by the appropriate operators */
extern EnumPropertyItem prop_driver_create_mapping_types[];
/* -------- */
/* Low-level call to add a new driver F-Curve. This shouldn't be used directly for most tools,
@ -244,8 +254,24 @@ struct FCurve *verify_driver_fcurve(struct ID *id, const char rna_path[], const
/* -------- */
/* Returns whether there is a driver in the copy/paste buffer to paste */
bool ANIM_driver_can_paste(void);
/* Main Driver Management API calls:
* Add a new driver for the specified property on the given ID block,
* and make it be driven by the specified target.
*
* This is intended to be used in conjunction with a modal "eyedropper"
* for picking the variable that is going to be used to drive this one.
*
* - flag: eCreateDriverFlags
* - driver_type: eDriver_Types
* - mapping_type: eCreateDriver_MappingTypes
*/
int ANIM_add_driver_with_target(
struct ReportList *reports,
struct ID *dst_id, const char dst_path[], int dst_index,
struct ID *src_id, const char src_path[], int src_index,
short flag, int driver_type, short mapping_type);
/* -------- */
/* Main Driver Management API calls:
* Add a new driver for the specified property on the given ID block
@ -257,6 +283,11 @@ int ANIM_add_driver(struct ReportList *reports, struct ID *id, const char rna_pa
*/
bool ANIM_remove_driver(struct ReportList *reports, struct ID *id, const char rna_path[], int array_index, short flag);
/* -------- */
/* Returns whether there is a driver in the copy/paste buffer to paste */
bool ANIM_driver_can_paste(void);
/* Main Driver Management API calls:
* Make a copy of the driver for the specified property on the given ID block
*/

@ -29,6 +29,7 @@
#include "MEM_guardedalloc.h"
#include "DNA_anim_types.h"
#include "DNA_space_types.h"
#include "DNA_screen_types.h"
#include "DNA_object_types.h"
@ -41,10 +42,13 @@
#include "BKE_context.h"
#include "BKE_screen.h"
#include "BKE_report.h"
#include "BKE_animsys.h"
#include "BKE_depsgraph.h"
#include "BKE_idcode.h"
#include "BKE_unit.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "BIF_gl.h"
@ -67,6 +71,9 @@
#include "ED_screen.h"
#include "ED_view3d.h"
/* for Driver eyedropper */
#include "ED_keyframing.h"
/* -------------------------------------------------------------------- */
/* Keymap
@ -112,6 +119,7 @@ wmKeyMap *eyedropper_modal_keymap(wmKeyConfig *keyconf)
WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_color");
WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_id");
WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_depth");
WM_modalkeymap_assign(keymap, "UI_OT_eyedropper_driver");
return keymap;
}
@ -1026,3 +1034,190 @@ void UI_OT_eyedropper_depth(wmOperatorType *ot)
}
/** \} */
/* -------------------------------------------------------------------- */
/* Eyedropper
*/
/* NOTE: This is here (instead of in drivers.c) because we need access the button internals,
* which we cannot access outside of the interface module
*/
/** \name Eyedropper (Driver Target)
* \{ */
typedef struct DriverDropper {
/* Destination property (i.e. where we'll add a driver) */
PointerRNA ptr;
PropertyRNA *prop;
int index;
// TODO: new target?
} DriverDropper;
static bool driverdropper_init(bContext *C, wmOperator *op)
{
DriverDropper *ddr;
uiBut *but;
op->customdata = ddr = MEM_callocN(sizeof(DriverDropper), "DriverDropper");
UI_context_active_but_prop_get(C, &ddr->ptr, &ddr->prop, &ddr->index);
but = UI_context_active_but_get(C);
if ((ddr->ptr.data == NULL) ||
(ddr->prop == NULL) ||
(RNA_property_editable(&ddr->ptr, ddr->prop) == false) ||
(RNA_property_animateable(&ddr->ptr, ddr->prop) == false) ||
(but->flag & UI_BUT_DRIVEN))
{
return false;
}
return true;
}
static void driverdropper_exit(bContext *C, wmOperator *op)
{
WM_cursor_modal_restore(CTX_wm_window(C));
if (op->customdata) {
MEM_freeN(op->customdata);
op->customdata = NULL;
}
}
static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event)
{
DriverDropper *ddr = (DriverDropper *)op->customdata;
wmWindow *win = CTX_wm_window(C);
ScrArea *sa = BKE_screen_find_area_xy(win->screen, SPACE_TYPE_ANY, event->x, event->y);
ARegion *ar = BKE_area_find_region_xy(sa, RGN_TYPE_ANY, event->x, event->y);
uiBut *but = ui_but_find_mouse_over(ar, event);
short mapping_type = RNA_enum_get(op->ptr, "mapping_type");
short flag = 0;
/* we can only add a driver if we know what RNA property it corresponds to */
if (ELEM(NULL, but, but->rnapoin.data, but->rnaprop)) {
return;
}
else {
/* Get paths for src... */
PointerRNA *target_ptr = &but->rnapoin;
PropertyRNA *target_prop = but->rnaprop;
int target_index = but->rnaindex;
char *target_path = RNA_path_from_ID_to_property(target_ptr, target_prop);
/* ... and destination */
char *dst_path = BKE_animdata_driver_path_hack(C, &ddr->ptr, ddr->prop, NULL);
/* Now create driver(s) */
int success = ANIM_add_driver_with_target(op->reports,
ddr->ptr.id.data, dst_path, ddr->index,
target_ptr->id.data, target_path, target_index,
flag, DRIVER_TYPE_PYTHON, mapping_type);
if (success) {
/* send updates */
UI_context_update_anim_flag(C);
DAG_relations_tag_update(CTX_data_main(C));
DAG_id_tag_update(ddr->ptr.id.data, OB_RECALC_OB | OB_RECALC_DATA);
WM_event_add_notifier(C, NC_ANIMATION | ND_FCURVES_ORDER, NULL); // XXX
}
}
}
static void driverdropper_cancel(bContext *C, wmOperator *op)
{
driverdropper_exit(C, op);
}
/* main modal status check */
static int driverdropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
DriverDropper *ddr = (DriverDropper *)op->customdata;
/* handle modal keymap */
if (event->type == EVT_MODAL_MAP) {
switch (event->val) {
case EYE_MODAL_CANCEL:
driverdropper_cancel(C, op);
return OPERATOR_CANCELLED;
case EYE_MODAL_SAMPLE_CONFIRM:
driverdropper_sample(C, op, event);
driverdropper_exit(C, op);
return OPERATOR_FINISHED;
}
}
return OPERATOR_RUNNING_MODAL;
}
/* Modal Operator init */
static int driverdropper_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
/* init */
if (driverdropper_init(C, op)) {
WM_cursor_modal_set(CTX_wm_window(C), BC_EYEDROPPER_CURSOR);
/* add temp handler */
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
else {
driverdropper_exit(C, op);
return OPERATOR_CANCELLED;
}
}
/* Repeat operator */
static int driverdropper_exec(bContext *C, wmOperator *op)
{
/* init */
if (driverdropper_init(C, op)) {
/* cleanup */
driverdropper_exit(C, op);
return OPERATOR_FINISHED;
}
else {
return OPERATOR_CANCELLED;
}
}
static int driverdropper_poll(bContext *C)
{
if (!CTX_wm_window(C)) return 0;
else return 1;
}
void UI_OT_eyedropper_driver(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Eyedropper Driver";
ot->idname = "UI_OT_eyedropper_driver";
ot->description = "Pick a property to use as a driver target";
/* api callbacks */
ot->invoke = driverdropper_invoke;
ot->modal = driverdropper_modal;
ot->cancel = driverdropper_cancel;
ot->exec = driverdropper_exec;
ot->poll = driverdropper_poll;
/* flags */
ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
/* properties */
RNA_def_enum(ot->srna, "mapping_type", prop_driver_create_mapping_types, 0,
"Mapping Type", "Method used to match target and driven properties");
}
/** \} */

@ -397,7 +397,6 @@ static bool ui_but_is_interactive(const uiBut *but, const bool labeledit);
static bool ui_but_contains_pt(uiBut *but, float mx, float my);
static bool ui_but_contains_point_px(ARegion *ar, uiBut *but, int x, int y);
static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, const bool labeledit);
static uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event);
static void button_activate_init(bContext *C, ARegion *ar, uiBut *but, uiButtonActivateType type);
static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state);
static void button_activate_exit(
@ -7506,7 +7505,7 @@ static uiBut *ui_but_find_mouse_over_ex(ARegion *ar, const int x, const int y, c
return butover;
}
static uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event)
uiBut *ui_but_find_mouse_over(ARegion *ar, const wmEvent *event)
{
return ui_but_find_mouse_over_ex(ar, event->x, event->y, event->ctrl != 0);
}

@ -660,6 +660,7 @@ extern int ui_but_menu_direction(uiBut *but);
extern void ui_but_text_password_hide(char password_str[UI_MAX_DRAW_STR], uiBut *but, const bool restore);
extern uiBut *ui_but_find_select_in_enum(uiBut *but, int direction);
extern uiBut *ui_but_find_active_in_region(struct ARegion *ar);
extern uiBut *ui_but_find_mouse_over(struct ARegion *ar, const struct wmEvent *event);
bool ui_but_is_editable(const uiBut *but);
bool ui_but_is_editable_as_text(const uiBut *but);
void ui_but_pie_dir_visual(RadialDirection dir, float vec[2]);
@ -745,5 +746,6 @@ struct wmKeyMap *eyedropper_modal_keymap(struct wmKeyConfig *keyconf);
void UI_OT_eyedropper_color(struct wmOperatorType *ot);
void UI_OT_eyedropper_id(struct wmOperatorType *ot);
void UI_OT_eyedropper_depth(struct wmOperatorType *ot);
void UI_OT_eyedropper_driver(struct wmOperatorType *ot);
#endif /* __INTERFACE_INTERN_H__ */

@ -1107,6 +1107,7 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_id);
WM_operatortype_append(UI_OT_eyedropper_depth);
WM_operatortype_append(UI_OT_eyedropper_driver);
}
/**