Add a new Copy As Driver context menu option for properties.
authorAlexander Gavrilov <angavrilov@gmail.com>
Wed, 31 Jul 2019 15:42:03 +0000 (18:42 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Sun, 4 Aug 2019 10:58:15 +0000 (13:58 +0300)
It is a very common need to create drivers that set the value of
a property to the value of some other property, but it currently
requires multiple actions: Copy Data Path on the input property,
adding a driver to the output property, selecting the input ID
reference, and pasting the path.

This adds a new Copy As Driver context menu option, which creates
a complete driver in the clipboard that reads the current property,
so all that remains is to paste it to the output property. It is
also possible to paste just the new driver variable into an existing
driver to combine multiple inputs.

Reviewers: brecht, billreynish

Differential Revision: https://developer.blender.org/D5382

source/blender/editors/animation/drivers.c
source/blender/editors/include/ED_keyframing.h
source/blender/editors/interface/interface_context_menu.c
source/blender/editors/interface/interface_ops.c

index 7ca0f95d6c46cabd818a45bb2576f5ae725d5278..935d11a388f11b2bec0584df757549807e3f36c5 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <stdio.h>
 #include <string.h>
+#include <ctype.h>
 
 #include "MEM_guardedalloc.h"
 
@@ -95,56 +96,65 @@ FCurve *verify_driver_fcurve(ID *id, const char rna_path[], const int array_inde
 
   if ((fcu == NULL) && (add)) {
     /* use default settings to make a F-Curve */
-    fcu = MEM_callocN(sizeof(FCurve), "FCurve");
+    fcu = alloc_driver_fcurve(rna_path, array_index, add);
 
-    fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
-    fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
+    /* just add F-Curve to end of driver list */
+    BLI_addtail(&adt->drivers, fcu);
+  }
 
-    /* store path - make copy, and store that */
-    fcu->rna_path = BLI_strdup(rna_path);
-    fcu->array_index = array_index;
-
-    /* If add is negative, don't init this data yet,
-     * since it will be filled in by the pasted driver. */
-    if (add > 0) {
-      BezTriple *bezt;
-      size_t i;
-
-      /* add some new driver data */
-      fcu->driver = MEM_callocN(sizeof(ChannelDriver), "ChannelDriver");
-
-      /* F-Modifier or Keyframes? */
-      // FIXME: replace these magic numbers with defines
-      if (add == 2) {
-        /* Python API Backwards compatibility hack:
-         * Create FModifier so that old scripts won't break
-         * for now before 2.7 series -- (September 4, 2013)
-         */
-        add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
-      }
-      else {
-        /* add 2 keyframes so that user has something to work with
-         * - These are configured to 0,0 and 1,1 to give a 1-1 mapping
-         *   which can be easily tweaked from there.
-         */
-        insert_vert_fcurve(fcu, 0.0f, 0.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
-        insert_vert_fcurve(fcu, 1.0f, 1.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
+  /* return the F-Curve */
+  return fcu;
+}
 
-        /* configure this curve to extrapolate */
-        for (i = 0, bezt = fcu->bezt; (i < fcu->totvert) && bezt; i++, bezt++) {
-          bezt->h1 = bezt->h2 = HD_VECT;
-        }
+struct FCurve *alloc_driver_fcurve(const char rna_path[], const int array_index, short add)
+{
+  FCurve *fcu = MEM_callocN(sizeof(FCurve), "FCurve");
 
-        fcu->extend = FCURVE_EXTRAPOLATE_LINEAR;
-        calchandles_fcurve(fcu);
-      }
+  fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
+  fcu->auto_smoothing = FCURVE_SMOOTH_CONT_ACCEL;
+
+  /* store path - make copy, and store that */
+  if (rna_path) {
+    fcu->rna_path = BLI_strdup(rna_path);
+  }
+  fcu->array_index = array_index;
+
+  /* If add is negative, don't init this data yet,
+   * since it will be filled in by the pasted driver. */
+  if (add > 0) {
+    BezTriple *bezt;
+    size_t i;
+
+    /* add some new driver data */
+    fcu->driver = MEM_callocN(sizeof(ChannelDriver), "ChannelDriver");
+
+    /* F-Modifier or Keyframes? */
+    // FIXME: replace these magic numbers with defines
+    if (add == 2) {
+      /* Python API Backwards compatibility hack:
+       * Create FModifier so that old scripts won't break
+       * for now before 2.7 series -- (September 4, 2013)
+       */
+      add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
     }
+    else {
+      /* add 2 keyframes so that user has something to work with
+       * - These are configured to 0,0 and 1,1 to give a 1-1 mapping
+       *   which can be easily tweaked from there.
+       */
+      insert_vert_fcurve(fcu, 0.0f, 0.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
+      insert_vert_fcurve(fcu, 1.0f, 1.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_FAST);
 
-    /* just add F-Curve to end of driver list */
-    BLI_addtail(&adt->drivers, fcu);
+      /* configure this curve to extrapolate */
+      for (i = 0, bezt = fcu->bezt; (i < fcu->totvert) && bezt; i++, bezt++) {
+        bezt->h1 = bezt->h2 = HD_VECT;
+      }
+
+      fcu->extend = FCURVE_EXTRAPOLATE_LINEAR;
+      calchandles_fcurve(fcu);
+    }
   }
 
-  /* return the F-Curve */
   return fcu;
 }
 
@@ -834,6 +844,48 @@ bool ANIM_driver_vars_paste(ReportList *reports, FCurve *fcu, bool replace)
   return true;
 }
 
+/* -------------------------------------------------- */
+
+/* Create a driver & variable that reads the specified property,
+ * and store it in the buffers for Paste Driver and Paste Variables. */
+void ANIM_copy_as_driver(struct ID *target_id, const char *target_path, const char *var_name)
+{
+  /* Clear copy/paste buffer first (for consistency with other copy/paste buffers). */
+  ANIM_drivers_copybuf_free();
+  ANIM_driver_vars_copybuf_free();
+
+  /* Create a dummy driver F-Curve. */
+  FCurve *fcu = alloc_driver_fcurve(NULL, 0, 1);
+  ChannelDriver *driver = fcu->driver;
+
+  /* Create a variable. */
+  DriverVar *var = driver_add_new_variable(driver);
+  DriverTarget *target = &var->targets[0];
+
+  target->idtype = GS(target_id->name);
+  target->id = target_id;
+  target->rna_path = MEM_dupallocN(target_path);
+
+  /* Set the variable name. */
+  if (var_name) {
+    BLI_strncpy(var->name, var_name, sizeof(var->name));
+
+    /* Sanitize the name. */
+    for (int i = 0; var->name[i]; i++) {
+      if (!(i > 0 ? isalnum(var->name[i]) : isalpha(var->name[i]))) {
+        var->name[i] = '_';
+      }
+    }
+  }
+
+  BLI_strncpy(driver->expression, var->name, sizeof(driver->expression));
+
+  /* Store the driver into the copy/paste buffers. */
+  channeldriver_copypaste_buf = fcu;
+
+  driver_variables_copy(&driver_vars_copybuf, &driver->variables);
+}
+
 /* ************************************************** */
 /* UI-Button Interface */
 
index 8f197fa9afe2672dc4dd92ce64f2943f46b02cbf..bbeeeade8225c6129cb0a4772768a192f731a7cd 100644 (file)
@@ -320,6 +320,8 @@ struct FCurve *verify_driver_fcurve(struct ID *id,
                                     const int array_index,
                                     short add);
 
+struct FCurve *alloc_driver_fcurve(const char rna_path[], const int array_index, short add);
+
 /* -------- */
 
 /* Main Driver Management API calls:
@@ -399,6 +401,12 @@ bool ANIM_driver_vars_copy(struct ReportList *reports, struct FCurve *fcu);
 /* Paste the variables in the buffer to the given FCurve */
 bool ANIM_driver_vars_paste(struct ReportList *reports, struct FCurve *fcu, bool replace);
 
+/* -------- */
+
+/* Create a driver & variable that reads the specified property,
+ * and store it in the buffers for Paste Driver and Paste Variables. */
+void ANIM_copy_as_driver(struct ID *target_id, const char *target_path, const char *var_name);
+
 /* ************ Auto-Keyframing ********************** */
 /* Notes:
  * - All the defines for this (User-Pref settings and Per-Scene settings)
index 7cec8af46de8a9b51b83675b22057cff61b23908..88fe870408227f071953c3beb74b60917857ff46 100644 (file)
@@ -899,6 +899,13 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
             ICON_NONE,
             "UI_OT_copy_data_path_button");
 
+    if (ptr->id.data && ELEM(type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
+      uiItemO(layout,
+              CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy As New Driver"),
+              ICON_NONE,
+              "UI_OT_copy_as_driver_button");
+    }
+
     uiItemS(layout);
 
     if (type == PROP_STRING && ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) {
index f2b2a478ba97cccd4357068a8f7ab7674e50df78..f5a894d76207639561d48e2eeba9787ec9c17679 100644 (file)
@@ -65,6 +65,9 @@
 #include "ED_object.h"
 #include "ED_paint.h"
 
+/* for Copy As Driver */
+#include "ED_keyframing.h"
+
 /* only for UI_OT_editsource */
 #include "ED_screen.h"
 #include "BKE_main.h"
@@ -153,23 +156,92 @@ static void UI_OT_copy_data_path_button(wmOperatorType *ot)
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 }
 
-static bool copy_python_command_button_poll(bContext *C)
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Copy As Driver Operator
+ * \{ */
+
+static bool copy_as_driver_button_poll(bContext *C)
 {
-  uiBut *but = UI_context_active_but_get(C);
+  PointerRNA ptr;
+  PropertyRNA *prop;
+  char *path;
+  int index;
 
-  if (but && (but->optype != NULL)) {
-    return 1;
+  UI_context_active_but_prop_get(C, &ptr, &prop, &index);
+
+  if (ptr.id.data && ptr.data && prop &&
+      ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) {
+    path = RNA_path_from_ID_to_property(&ptr, prop);
+
+    if (path) {
+      MEM_freeN(path);
+      return 1;
+    }
   }
 
   return 0;
 }
 
+static int copy_as_driver_button_exec(bContext *C, wmOperator *UNUSED(op))
+{
+  PointerRNA ptr;
+  PropertyRNA *prop;
+  int index;
+
+  /* try to create driver using property retrieved from UI */
+  UI_context_active_but_prop_get(C, &ptr, &prop, &index);
+
+  if (ptr.id.data && ptr.data && prop) {
+    int dim = RNA_property_array_dimension(&ptr, prop, NULL);
+    char *path = RNA_path_from_ID_to_property_index(&ptr, prop, dim, index);
+
+    if (path) {
+      ANIM_copy_as_driver(ptr.id.data, path, RNA_property_identifier(prop));
+      MEM_freeN(path);
+      return OPERATOR_FINISHED;
+    }
+  }
+
+  return OPERATOR_CANCELLED;
+}
+
+static void UI_OT_copy_as_driver_button(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Copy As New Driver";
+  ot->idname = "UI_OT_copy_as_driver_button";
+  ot->description =
+      "Create a new driver with this property as input, and copy it to the "
+      "clipboard. Use Paste Driver to add it to the target property, or Paste "
+      "Driver Variables to extend an existing driver";
+
+  /* callbacks */
+  ot->exec = copy_as_driver_button_exec;
+  ot->poll = copy_as_driver_button_poll;
+
+  /* flags */
+  ot->flag = OPTYPE_REGISTER;
+}
+
 /** \} */
 
 /* -------------------------------------------------------------------- */
 /** \name Copy Python Command Operator
  * \{ */
 
+static bool copy_python_command_button_poll(bContext *C)
+{
+  uiBut *but = UI_context_active_but_get(C);
+
+  if (but && (but->optype != NULL)) {
+    return 1;
+  }
+
+  return 0;
+}
+
 static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op))
 {
   uiBut *but = UI_context_active_but_get(C);
@@ -1668,6 +1740,7 @@ static void UI_OT_drop_color(wmOperatorType *ot)
 void ED_operatortypes_ui(void)
 {
   WM_operatortype_append(UI_OT_copy_data_path_button);
+  WM_operatortype_append(UI_OT_copy_as_driver_button);
   WM_operatortype_append(UI_OT_copy_python_command_button);
   WM_operatortype_append(UI_OT_reset_default_button);
   WM_operatortype_append(UI_OT_assign_default_button);