diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c index 1f77ddc73b1..65a235e88f6 100644 --- a/source/blender/editors/animation/drivers.c +++ b/source/blender/editors/animation/drivers.c @@ -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); - - MEM_freeN(path); - } + WM_operator_properties_create_ptr(&op_ptr, ot); + + 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) diff --git a/source/blender/editors/include/ED_keyframing.h b/source/blender/editors/include/ED_keyframing.h index 02b22bdbf42..eb21a431702 100644 --- a/source/blender/editors/include/ED_keyframing.h +++ b/source/blender/editors/include/ED_keyframing.h @@ -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 */ diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c index 41a04698756..5120a5e1bd7 100644 --- a/source/blender/editors/interface/interface_eyedropper.c +++ b/source/blender/editors/interface/interface_eyedropper.c @@ -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"); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c index 3e495615e07..83635822765 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -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); } diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 9f01ae4d618..f02aad1ff87 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -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__ */ diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 356abe1a92c..bacae0a28c6 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -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); } /**