[Bf-blender-cvs] [2246d456aa8] master: Animation: Allow selection of FCurve + its keys

Maxime Casas noreply at git.blender.org
Tue Jun 8 15:46:03 CEST 2021


Commit: 2246d456aa848f0e6b41ed90ff44869e79cb8def
Author: Maxime Casas
Date:   Tue Jun 8 15:32:47 2021 +0200
Branches: master
https://developer.blender.org/rB2246d456aa848f0e6b41ed90ff44869e79cb8def

Animation: Allow selection of FCurve + its keys

Selection of an FCurve with box/circle select now selects the entire
curve and all its keys:

- Box selecting a curve selects all the keyframes of the curve.
- Ctrl + box selecting of the curve deselects all the keyframes of the
  curve.
- Shift + box selecting of the curve extends the keyframe selection,
  adding all the keyframes of the curves that were just selected to the
  selection.
- In all cases, if the selection area contains a key, nothing is
  performed on the curves themselves (the action only impacts the
  selected keys).

Reviewed By: sybren, #animation_rigging

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

===================================================================

M	source/blender/editors/space_graph/graph_select.c

===================================================================

diff --git a/source/blender/editors/space_graph/graph_select.c b/source/blender/editors/space_graph/graph_select.c
index 4ab4ef518fb..b9c235873a7 100644
--- a/source/blender/editors/space_graph/graph_select.c
+++ b/source/blender/editors/space_graph/graph_select.c
@@ -512,16 +512,19 @@ static rctf initialize_box_select_coords(const bAnimContext *ac, const rctf *rec
   return rectf;
 }
 
-static ListBase initialize_box_select_anim_data(const SpaceGraph *sipo, bAnimContext *ac)
+static int initialize_animdata_selection_filter(const SpaceGraph *sipo)
 {
-  ListBase anim_data = {NULL, NULL};
-
   int filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_NODUPLIS);
   if (sipo->flag & SIPO_SELCUVERTSONLY) {
     filter |= ANIMFILTER_FOREDIT | ANIMFILTER_SELEDIT;
   }
-  ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+  return filter;
+}
 
+static ListBase initialize_box_select_anim_data(const int filter, bAnimContext *ac)
+{
+  ListBase anim_data = {NULL, NULL};
+  ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
   return anim_data;
 }
 
@@ -573,8 +576,11 @@ static void initialize_box_select_key_editing_data(const SpaceGraph *sipo,
  * which means that they may be inadvertently moved as well. However, incl_handles overrides
  * this, and allow handles to be considered independently too.
  * Also, for convenience, handles should get same status as keyframe (if it was within bounds).
+ *
+ * This function returns true if there was any change in the selection of a key (selecting or
+ * deselecting any key returns true, otherwise it returns false).
  */
-static void box_select_graphkeys(bAnimContext *ac,
+static bool box_select_graphkeys(bAnimContext *ac,
                                  const rctf *rectf_view,
                                  short mode,
                                  short selectmode,
@@ -583,7 +589,8 @@ static void box_select_graphkeys(bAnimContext *ac,
 {
   const rctf rectf = initialize_box_select_coords(ac, rectf_view);
   SpaceGraph *sipo = (SpaceGraph *)ac->sl;
-  ListBase anim_data = initialize_box_select_anim_data(sipo, ac);
+  const int filter = initialize_animdata_selection_filter(sipo);
+  ListBase anim_data = initialize_box_select_anim_data(filter, ac);
   rctf scaled_rectf;
   KeyframeEditData ked;
   int mapping_flag;
@@ -597,6 +604,9 @@ static void box_select_graphkeys(bAnimContext *ac,
   /* Try selecting the keyframes. */
   bAnimListElem *ale = NULL;
 
+  /* This variable will be set to true if any key is selected or deselected. */
+  bool any_key_selection_changed = false;
+
   /* First loop over data, doing box select. try selecting keys only. */
   for (ale = anim_data.first; ale; ale = ale->next) {
     AnimData *adt = ANIM_nla_mapping_get(ac, ale);
@@ -634,7 +644,7 @@ static void box_select_graphkeys(bAnimContext *ac,
     if (ANIM_fcurve_keyframes_loop(&ked, fcu, NULL, ok_cb, NULL)) {
       /* select keyframes that are in the appropriate places */
       ANIM_fcurve_keyframes_loop(&ked, fcu, ok_cb, select_cb, NULL);
-
+      any_key_selection_changed = true;
       /* Only change selection of channel when the visibility of keyframes
        * doesn't depend on this. */
       if ((sipo->flag & SIPO_SELCUVERTSONLY) == 0) {
@@ -653,6 +663,125 @@ static void box_select_graphkeys(bAnimContext *ac,
 
   /* Cleanup. */
   ANIM_animdata_freelist(&anim_data);
+
+  return any_key_selection_changed;
+}
+
+/* This function is used to set all the keyframes of a given curve as selectable
+ * by the "select_cb" function inside of "box_select_graphcurves".
+ */
+static short ok_bezier_always_ok(KeyframeEditData *UNUSED(ked), BezTriple *UNUSED(bezt))
+{
+  return KEYFRAME_OK_KEY | KEYFRAME_OK_H1 | KEYFRAME_OK_H2;
+}
+
+/* Checks whether the given rectangle intersects the given fcurve's calculated curve (i.e. not
+ * only keyframes, but also all the interpolated values). This is done by sampling the curve at
+ * different points between the xmin and the xmax of the rectangle.
+ */
+static bool rectf_curve_intersection(
+    const float offset, const float unit_scale, const rctf *rectf, AnimData *adt, FCurve *fcu)
+{
+  /* 30 sampling points. This worked well in tests. */
+  const float num_steps = 30.0f;
+  const float step = (rectf->xmax - rectf->xmin) / num_steps;
+
+  /* Remap the range at which to evaluate the fcurves. This enables us to avoid remapping
+   * the keys themselves. */
+  const float mapped_max = BKE_nla_tweakedit_remap(adt, rectf->xmax, NLATIME_CONVERT_UNMAP);
+  const float mapped_min = BKE_nla_tweakedit_remap(adt, rectf->xmin, NLATIME_CONVERT_UNMAP);
+  const float eval_step = (mapped_max - mapped_min) / num_steps;
+
+  float x = rectf->xmin;
+  float eval_x = mapped_min;
+  /* Sample points on the given fcurve in the interval defined by the
+   * mapped_min and mapped_max of the selected rectangle.
+   * For each point, check if it is inside of the selection box. If it is, then select
+   * all the keyframes of the curve, the curve, and stop the loop.
+   */
+  while (x < rectf->xmax) {
+    const float fcurve_y = (evaluate_fcurve(fcu, eval_x) + offset) * unit_scale;
+    /* Since rectf->xmin <= x < rectf->xmax is always true, there is no need to keep comparing the
+     * X-coordinate to the rectangle in every iteration. Therefore we do the comparisons manually
+     * instead of using BLI_rctf_isect_pt_v(rectf, current_point).
+     */
+    if (rectf->ymin <= fcurve_y && fcurve_y <= rectf->ymax) {
+      return true;
+    }
+    x += step;
+    eval_x += eval_step;
+  }
+  return false;
+}
+
+/* Perform a box selection of the curves themselves. This means this function tries
+ * to select a curve by sampling it at various points instead of trying to select the
+ * keyframes directly.
+ * The selection actions done to a curve are actually done on all the keyframes of the curve.
+ * Note: This function is only called if no keyframe is in the seletion area.
+ */
+static void box_select_graphcurves(bAnimContext *ac,
+                                   const rctf *rectf_view,
+                                   const short mode,
+                                   const short selectmode,
+                                   const bool incl_handles,
+                                   void *data)
+{
+  const SpaceGraph *sipo = (SpaceGraph *)ac->sl;
+  const int filter = initialize_animdata_selection_filter(sipo);
+  ListBase anim_data = initialize_box_select_anim_data(filter, ac);
+  rctf scaled_rectf;
+  KeyframeEditData ked;
+  int mapping_flag;
+  initialize_box_select_key_editing_data(
+      sipo, incl_handles, mode, ac, data, &scaled_rectf, &ked, &mapping_flag);
+
+  FCurve *last_selected_curve = NULL;
+
+  /* Go through all the curves and try selecting them. This function is only called
+   * if no keyframe is in the seletion area, so we only have to check if the curve
+   * intersects the area in order to check if the selection/deselection must happen.
+   */
+
+  LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
+    AnimData *adt = ANIM_nla_mapping_get(ac, ale);
+    FCurve *fcu = (FCurve *)ale->key_data;
+    float offset;
+    const float unit_scale = ANIM_unit_mapping_get_factor(
+        ac->scene, ale->id, fcu, mapping_flag, &offset);
+
+    const rctf rectf = initialize_box_select_coords(ac, rectf_view);
+
+    /* scaled_rectf is declared at the top of the block because it is required by the
+     * initialize_box_select_key_editing_data function (which does
+     * data_xxx->rectf_scaled = scaled_rectf). The below assignment therefore modifies the
+     * data we use to iterate over the curves (ked).
+     */
+    scaled_rectf.xmin = rectf.xmin;
+    scaled_rectf.xmax = rectf.xmax;
+    scaled_rectf.ymin = rectf.ymin / unit_scale - offset;
+    scaled_rectf.ymax = rectf.ymax / unit_scale - offset;
+
+    const KeyframeEditFunc select_cb = ANIM_editkeyframes_select(selectmode);
+    if (rectf_curve_intersection(offset, unit_scale, &rectf, adt, fcu)) {
+      if ((selectmode & SELECT_ADD) || (selectmode & SELECT_REPLACE)) {
+        fcu->flag |= FCURVE_SELECTED;
+        last_selected_curve = fcu;
+      }
+      else {
+        fcu->flag &= ~FCURVE_SELECTED;
+      }
+      ANIM_fcurve_keyframes_loop(&ked, fcu, ok_bezier_always_ok, select_cb, NULL);
+    }
+  }
+
+  /* Make sure that one of the selected curves is active in the end. */
+  if (last_selected_curve != NULL) {
+    ANIM_set_active_channel(
+        ac, ac->data, ac->datatype, filter, last_selected_curve, ANIMTYPE_FCURVE);
+  }
+
+  ANIM_animdata_freelist(&anim_data);
 }
 
 /* ------------------- */
@@ -726,7 +855,12 @@ static int graphkeys_box_select_exec(bContext *C, wmOperator *op)
   BLI_rctf_rcti_copy(&rect_fl, &rect);
 
   /* Apply box_select action. */
-  box_select_graphkeys(&ac, &rect_fl, mode, selectmode, incl_handles, NULL);
+  const bool any_key_selection_changed = box_select_graphkeys(
+      &ac, &rect_fl, mode, selectmode, incl_handles, NULL);
+  const bool use_curve_selection = RNA_boolean_get(op->ptr, "use_curve_selection");
+  if (use_curve_selection && !any_key_selection_changed) {
+    box_select_graphcurves(&ac, &rect_fl, mode, selectmode, incl_handles, NULL);
+  }
   /* Send notifier that keyframe selection has changed. */
   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_SELECTED, NULL);
 
@@ -767,6 +901,14 @@ void GRAPH_OT_select_box(wmOperatorType *ot)
       ot->srna, "tweak", 0, "Tweak", "Operator has been activated using a tweak event");
   RNA_def_property_flag(prop, PROP_SKIP_SAVE);
 
+  prop = RNA_def_boolean(
+      ot->srna,
+      "use_curve_selection",
+      1,
+      "Select Curves",
+      "Allow selecting all the keyframes of a curve by selecting the calculated fcurve");
+  RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
   WM_operator_properties_gesture_box(ot);
   WM_operator_properties_select_operation_simple(ot);
 }
@@ -815,7 +957,13 @@ static int graphkeys_lassoselect_exec(bContext *C, wmOperator *op)
   BLI_rctf_rcti_copy(&rect_fl, &rect);
 
   /* Apply box_select action. */
-  box_select_graphkeys(&ac, &rect_fl, BEZT_OK_REGION_LASSO, selectmode, incl_handles, &data_lasso);
+  const bool any_key_selection_ch

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list