Make curve decimation only take into account the selected curve points
authorSebastian Parborg <darkdefende@gmail.com>
Wed, 4 Dec 2019 15:00:03 +0000 (16:00 +0100)
committerSebastian Parborg <darkdefende@gmail.com>
Wed, 4 Dec 2019 15:02:58 +0000 (16:02 +0100)
Previously the decimation would take the whole curve into account when
decimating and not just the selected part.

This also contains various smaller bug fixes for the fcurve decimation.

Reviewed By: Sybren

Differential Revision: http://developer.blender.org/D6286

12 files changed:
release/scripts/startup/bl_ui/space_graph.py
source/blender/blenkernel/intern/curve_decimate.c
source/blender/editors/animation/keyframes_general.c
source/blender/editors/include/ED_keyframes_edit.h
source/blender/editors/include/UI_view2d.h
source/blender/editors/interface/interface_region_hud.c
source/blender/editors/interface/view2d.c
source/blender/editors/screen/area.c
source/blender/editors/space_graph/graph_edit.c
source/blender/editors/space_graph/space_graph.c
source/blender/makesdna/DNA_screen_types.h
source/blender/makesrna/intern/rna_space.c

index 188741956ab74a7fd10a2474fe6875145b6867a3..752e1741984e97a601faf8b1a2b5e552816e6674 100644 (file)
@@ -113,6 +113,7 @@ class GRAPH_MT_view(Menu):
         st = context.space_data
 
         layout.prop(st, "show_region_ui")
+        layout.prop(st, "show_region_hud")
         layout.separator()
 
         layout.prop(st, "use_realtime_update")
@@ -290,7 +291,14 @@ class GRAPH_MT_key(Menu):
         layout.operator_menu_enum("graph.easing_type", "type", text="Easing Type")
 
         layout.separator()
-        layout.operator("graph.decimate")
+        operator_context = layout.operator_context
+
+        layout.operator("graph.decimate", text="Decimate (Ratio)").mode = 'RATIO'
+        # Using the modal operation doesn't make sense for this variant as we do not have a modal mode for it, so just execute it.
+        layout.operator_context = 'EXEC_DEFAULT'
+        layout.operator("graph.decimate", text="Decimate (Allowed Change)").mode = 'ERROR'
+        layout.operator_context = operator_context
+
         layout.operator("graph.clean").channels = False
         layout.operator("graph.clean", text="Clean Channels").channels = True
         layout.operator("graph.smooth")
index cccdf830854151f49635b3c827041c0e221f37eb..d569684d55ceceed3bbb5d0fd817a404f302695c 100644 (file)
@@ -269,11 +269,11 @@ uint BKE_curve_decimate_bezt_array(BezTriple *bezt_array,
     if (a == HD_VECT) { \
       a = HD_FREE; \
     } \
-    else if (a == HD_AUTO) { \
+    else if (a == HD_AUTO || a == HD_AUTO_ANIM) { \
       a = HD_ALIGN; \
     } \
     /* opposite handle */ \
-    if (b == HD_AUTO) { \
+    if (b == HD_AUTO || b == HD_AUTO_ANIM) { \
       b = HD_ALIGN; \
     } \
   } \
index ffae402989c80a952ee25bd850309d5f7b42950f..ed5cb65c42e8c0ba8e4f74a6cdc080ff4be137e7 100644 (file)
@@ -328,34 +328,135 @@ void clean_fcurve(struct bAnimContext *ac, bAnimListElem *ale, float thresh, boo
 
 /* ---------------- */
 
+/* Check if the keyframe interpolation type is supported */
+static bool prepare_for_decimate(FCurve *fcu, int i)
+{
+  switch (fcu->bezt[i].ipo) {
+    case BEZT_IPO_BEZ:
+      /* We do not need to do anything here as the keyframe already has the required setting.
+       */
+      return true;
+    case BEZT_IPO_LIN:
+      /* Convert to a linear bezt curve to be able to use the decimation algorithm. */
+      fcu->bezt[i].ipo = BEZT_IPO_BEZ;
+      fcu->bezt[i].h1 = HD_FREE;
+      fcu->bezt[i].h2 = HD_FREE;
+
+      if (i != 0) {
+        float h1[3];
+        sub_v3_v3v3(h1, fcu->bezt[i - 1].vec[1], fcu->bezt[i].vec[1]);
+        mul_v3_fl(h1, 1.0f / 3.0f);
+        add_v3_v3(h1, fcu->bezt[i].vec[1]);
+        copy_v3_v3(fcu->bezt[i].vec[0], h1);
+      }
+
+      if (i + 1 != fcu->totvert) {
+        float h2[3];
+        sub_v3_v3v3(h2, fcu->bezt[i + 1].vec[1], fcu->bezt[i].vec[1]);
+        mul_v3_fl(h2, 1.0f / 3.0f);
+        add_v3_v3(h2, fcu->bezt[i].vec[1]);
+        copy_v3_v3(fcu->bezt[i].vec[2], h2);
+      }
+      return true;
+    default:
+      /* These are unsupported. */
+      return false;
+  }
+}
+
+/* Decimate the given curve segment. */
+static void decimate_fcurve_segment(FCurve *fcu,
+                                    int bezt_segment_start_idx,
+                                    int bezt_segment_len,
+                                    float remove_ratio,
+                                    float error_sq_max)
+{
+  int selected_len = bezt_segment_len;
+
+  /* Make sure that we can remove the start/end point of the segment if they
+   * are not the start/end point of the curve. BKE_curve_decimate_bezt_array
+   * has a check that prevents removal of the first and last index in the
+   * passed array. */
+  if (bezt_segment_len + bezt_segment_start_idx != fcu->totvert &&
+      prepare_for_decimate(fcu, bezt_segment_len + bezt_segment_start_idx)) {
+    bezt_segment_len++;
+  }
+  if (bezt_segment_start_idx != 0 && prepare_for_decimate(fcu, bezt_segment_start_idx - 1)) {
+    bezt_segment_start_idx--;
+    bezt_segment_len++;
+  }
+
+  const int target_fcurve_verts = ceil(bezt_segment_len - selected_len * remove_ratio);
+
+  BKE_curve_decimate_bezt_array(&fcu->bezt[bezt_segment_start_idx],
+                                bezt_segment_len,
+                                12, /* The actual resolution displayed in the viewport is dynamic
+                                       so we just pick a value that preserves the curve shape. */
+                                false,
+                                SELECT,
+                                BEZT_FLAG_TEMP_TAG,
+                                error_sq_max,
+                                target_fcurve_verts);
+}
+
 /**
  * F-Curve 'decimate' function that removes a certain ratio of curve
  * points that will affect the curves overall shape the least.
+ * If you want to remove based on a error margin, set remove_ratio to 1 and
+ * simply specify the desired error_sq_max. Otherwise, set the error margin to
+ * FLT_MAX.
  */
-void decimate_fcurve(bAnimListElem *ale, float remove_ratio)
+bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max)
 {
   FCurve *fcu = (FCurve *)ale->key_data;
 
   /* Check if the curve actually has any points  */
   if (fcu == NULL || fcu->bezt == NULL || fcu->totvert == 0) {
-    return;
+    return true;
   }
 
-  const int target_fcurve_verts = max_ii(2, fcu->totvert - fcu->totvert * remove_ratio);
-
   BezTriple *old_bezts = fcu->bezt;
 
-  if (target_fcurve_verts != fcu->totvert) {
-    /* We don't want to limit the decimation to a certain error margin */
-    const float error_sq_max = FLT_MAX;
-    BKE_curve_decimate_bezt_array(fcu->bezt,
-                                  fcu->totvert,
-                                  12, /* 12 is the resolution of graph editor curves */
-                                  false,
-                                  SELECT,
-                                  BEZT_FLAG_TEMP_TAG,
-                                  error_sq_max,
-                                  target_fcurve_verts);
+  /* Only decimate the individual selected curve segments. */
+  int bezt_segment_start_idx = 0;
+  int bezt_segment_len = 0;
+
+  bool selected;
+  bool can_decimate_all_selected = true;
+  bool in_segment = false;
+
+  for (int i = 0; i < fcu->totvert; i++) {
+    selected = fcu->bezt[i].f2 & SELECT;
+    /* Make sure that the temp flag is unset as we use it to determine what to remove. */
+    fcu->bezt[i].f2 &= ~BEZT_FLAG_TEMP_TAG;
+
+    if (selected && !prepare_for_decimate(fcu, i)) {
+      /* This keyframe is not supported, treat them as if they were unselected. */
+      selected = false;
+      can_decimate_all_selected = false;
+    }
+
+    if (selected) {
+      if (!in_segment) {
+        bezt_segment_start_idx = i;
+        in_segment = true;
+      }
+      bezt_segment_len++;
+    }
+    else if (in_segment) {
+      /* If the curve point is not selected then we have reached the end of the selected curve
+       * segment. */
+      decimate_fcurve_segment(
+          fcu, bezt_segment_start_idx, bezt_segment_len, remove_ratio, error_sq_max);
+      in_segment = false;
+      bezt_segment_len = 0;
+    }
+  }
+
+  /* Did the segment run to the end of the curve? */
+  if (in_segment) {
+    decimate_fcurve_segment(
+        fcu, bezt_segment_start_idx, bezt_segment_len, remove_ratio, error_sq_max);
   }
 
   uint old_totvert = fcu->totvert;
@@ -372,6 +473,8 @@ void decimate_fcurve(bAnimListElem *ale, float remove_ratio)
   if (old_bezts) {
     MEM_freeN(old_bezts);
   }
+
+  return can_decimate_all_selected;
 }
 
 /* ---------------- */
index 8181cebfe3ce3a6bc0d58e5ef15de1a5f10ab715..c347d75fa667e9d2f46f62bd13cc30609f7d02bc 100644 (file)
@@ -309,7 +309,7 @@ void clean_fcurve(struct bAnimContext *ac,
                   struct bAnimListElem *ale,
                   float thresh,
                   bool cleardefault);
-void decimate_fcurve(struct bAnimListElem *ale, float remove_ratio);
+bool decimate_fcurve(struct bAnimListElem *ale, float remove_ratio, float error_sq_max);
 void smooth_fcurve(struct FCurve *fcu);
 void sample_fcurve(struct FCurve *fcu);
 
index 0dbf3c710d66c6b7615cfa96b01e9e6d40910dd3..e34f40d057dc9f840e51d82df8187c8b6d02aeba 100644 (file)
@@ -220,6 +220,7 @@ bool UI_view2d_view_to_region_rcti_clip(struct View2D *v2d,
 struct View2D *UI_view2d_fromcontext(const struct bContext *C);
 struct View2D *UI_view2d_fromcontext_rwin(const struct bContext *C);
 
+void UI_view2d_scroller_size_get(const struct View2D *v2d, float *r_x, float *r_y);
 void UI_view2d_scale_get(struct View2D *v2d, float *r_x, float *r_y);
 float UI_view2d_scale_get_x(const struct View2D *v2d);
 float UI_view2d_scale_get_y(const struct View2D *v2d);
index 54bdbe0ec6e17d0681029effbc836f121d42964c..fa471441cc997622e0db45cfae965f0eab1daedd 100644 (file)
@@ -265,6 +265,14 @@ static ARegion *hud_region_add(ScrArea *sa)
   ar->overlap = true;
   ar->flag |= RGN_FLAG_DYNAMIC_SIZE;
 
+  if (ar_win) {
+    float x, y;
+
+    UI_view2d_scroller_size_get(&ar_win->v2d, &x, &y);
+    ar->runtime.offset_x = x;
+    ar->runtime.offset_y = y;
+  }
+
   return ar;
 }
 
index b1a060089eea275bee62ae260f10b07d4e354392..a3f84e7bdd059445050ab70e8f41b885d0282bac 100644 (file)
@@ -174,12 +174,9 @@ static void view2d_masks(View2D *v2d, bool check_scrollers, const rcti *mask_scr
    * - if they overlap, they must not occupy the corners (which are reserved for other widgets)
    */
   if (scroll) {
-    const int scroll_width = (v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES) ?
-                                 V2D_SCROLL_HANDLE_WIDTH :
-                                 V2D_SCROLL_WIDTH;
-    const int scroll_height = (v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES) ?
-                                  V2D_SCROLL_HANDLE_HEIGHT :
-                                  V2D_SCROLL_HEIGHT;
+    float scroll_width, scroll_height;
+
+    UI_view2d_scroller_size_get(v2d, &scroll_width, &scroll_height);
 
     /* vertical scroller */
     if (scroll & V2D_SCROLL_LEFT) {
@@ -1939,6 +1936,31 @@ View2D *UI_view2d_fromcontext_rwin(const bContext *C)
   return &(region->v2d);
 }
 
+/* Get scrollbar sizes of the current 2D view. The size will be zero if the view has its scrollbars
+ * disabled. */
+void UI_view2d_scroller_size_get(const View2D *v2d, float *r_x, float *r_y)
+{
+  int scroll = view2d_scroll_mapped(v2d->scroll);
+
+  if (r_x) {
+    if (scroll & V2D_SCROLL_VERTICAL) {
+      *r_x = (scroll & V2D_SCROLL_VERTICAL_HANDLES) ? V2D_SCROLL_HANDLE_WIDTH : V2D_SCROLL_WIDTH;
+    }
+    else {
+      *r_x = 0;
+    }
+  }
+  if (r_y) {
+    if (scroll & V2D_SCROLL_HORIZONTAL) {
+      *r_y = (scroll & V2D_SCROLL_HORIZONTAL_HANDLES) ? V2D_SCROLL_HANDLE_HEIGHT :
+                                                        V2D_SCROLL_HEIGHT;
+    }
+    else {
+      *r_y = 0;
+    }
+  }
+}
+
 /**
  * Calculate the scale per-axis of the drawing-area
  *
index ccee88eb0d6fe16d4bd81db40e66ebf462ce31e1..36a2b4c2893a993c117d570ecae35a9585e287cc 100644 (file)
@@ -1280,11 +1280,12 @@ static void region_rect_recursive(
      */
     const int size_min[2] = {UI_UNIT_X, UI_UNIT_Y};
     rcti overlap_remainder_margin = *overlap_remainder;
+
     BLI_rcti_resize(&overlap_remainder_margin,
                     max_ii(0, BLI_rcti_size_x(overlap_remainder) - UI_UNIT_X / 2),
                     max_ii(0, BLI_rcti_size_y(overlap_remainder) - UI_UNIT_Y / 2));
-    ar->winrct.xmin = overlap_remainder_margin.xmin;
-    ar->winrct.ymin = overlap_remainder_margin.ymin;
+    ar->winrct.xmin = overlap_remainder_margin.xmin + ar->runtime.offset_x;
+    ar->winrct.ymin = overlap_remainder_margin.ymin + ar->runtime.offset_y;
     ar->winrct.xmax = ar->winrct.xmin + prefsizex - 1;
     ar->winrct.ymax = ar->winrct.ymin + prefsizey - 1;
 
index e7a25f6c659e71a23df4999d3d8320afb0241d5a..2948566ff0edf8161797395aa15b11d81cd0a22d 100644 (file)
@@ -60,6 +60,7 @@
 #include "ED_anim_api.h"
 #include "ED_keyframing.h"
 #include "ED_keyframes_edit.h"
+#include "ED_numinput.h"
 #include "ED_screen.h"
 #include "ED_transform.h"
 #include "ED_markers.h"
@@ -1305,7 +1306,7 @@ void GRAPH_OT_clean(wmOperatorType *ot)
 
 /* ******************** Decimate Keyframes Operator ************************* */
 
-static void decimate_graph_keys(bAnimContext *ac, float remove_ratio)
+static void decimate_graph_keys(bAnimContext *ac, float remove_ratio, float error_sq_max)
 {
   ListBase anim_data = {NULL, NULL};
   bAnimListElem *ale;
@@ -1318,7 +1319,10 @@ static void decimate_graph_keys(bAnimContext *ac, float remove_ratio)
 
   /* loop through filtered data and clean curves */
   for (ale = anim_data.first; ale; ale = ale->next) {
-    decimate_fcurve(ale, remove_ratio);
+    if (!decimate_fcurve(ale, remove_ratio, error_sq_max)) {
+      /* The selection contains unsupported keyframe types! */
+      WM_report(RPT_WARNING, "Decimate: Skipping non linear/bezier keyframes!");
+    }
 
     ale->update |= ANIM_UPDATE_DEFAULT;
   }
@@ -1329,18 +1333,329 @@ static void decimate_graph_keys(bAnimContext *ac, float remove_ratio)
 
 /* ------------------- */
 
+/* This data type is only used for modal operation. */
+typedef struct tDecimateGraphOp {
+  bAnimContext ac;
+  Scene *scene;
+  ScrArea *sa;
+  ARegion *ar;
+
+  /** A 0-1 value for determining how much we should decimate. */
+  PropertyRNA *percentage_prop;
+
+  /** The original bezt curve data (used for restoring fcurves).*/
+  ListBase bezt_arr_list;
+
+  NumInput num;
+} tDecimateGraphOp;
+
+typedef struct tBeztCopyData {
+  int tot_vert;
+  BezTriple *bezt;
+} tBeztCopyData;
+
+typedef enum tDecimModes {
+  DECIM_RATIO = 1,
+  DECIM_ERROR,
+} tDecimModes;
+
+/* Overwrite the current bezts arrays with the original data. */
+static void decimate_reset_bezts(tDecimateGraphOp *dgo)
+{
+  ListBase anim_data = {NULL, NULL};
+  LinkData *link_bezt;
+  bAnimListElem *ale;
+  int filter;
+
+  bAnimContext *ac = &dgo->ac;
+
+  /* filter data */
+  filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT |
+            ANIMFILTER_SEL | ANIMFILTER_NODUPLIS);
+  ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+
+  /* Loop through filtered data and reset bezts. */
+  for (ale = anim_data.first, link_bezt = dgo->bezt_arr_list.first; ale;
+       ale = ale->next, link_bezt = link_bezt->next) {
+    FCurve *fcu = (FCurve *)ale->key_data;
+    tBeztCopyData *data = link_bezt->data;
+
+    const int arr_size = sizeof(BezTriple) * data->tot_vert;
+
+    MEM_freeN(fcu->bezt);
+
+    fcu->bezt = MEM_mallocN(arr_size, __func__);
+    fcu->totvert = data->tot_vert;
+
+    memcpy(fcu->bezt, data->bezt, arr_size);
+  }
+
+  ANIM_animdata_freelist(&anim_data);
+}
+
+static void decimate_exit(bContext *C, wmOperator *op)
+{
+  tDecimateGraphOp *dgo = op->customdata;
+  wmWindow *win = CTX_wm_window(C);
+
+  /* If data exists, clear its data and exit. */
+  if (dgo == NULL) {
+    return;
+  }
+  LinkData *link;
+
+  for (link = dgo->bezt_arr_list.first; link != NULL; link = link->next) {
+    tBeztCopyData *copy = link->data;
+    MEM_freeN(copy->bezt);
+    MEM_freeN(link->data);
+  }
+
+  BLI_freelistN(&dgo->bezt_arr_list);
+  MEM_freeN(dgo);
+
+  WM_cursor_modal_restore(win);
+
+  /* cleanup */
+  op->customdata = NULL;
+}
+
+/* Draw a percentage indicator in header. */
+static void decimate_draw_status_header(wmOperator *op, tDecimateGraphOp *dgo)
+{
+  char status_str[UI_MAX_DRAW_STR];
+  char mode_str[32];
+
+  strcpy(mode_str, TIP_("Decimate Keyframes"));
+
+  if (hasNumInput(&dgo->num)) {
+    char str_offs[NUM_STR_REP_LEN];
+
+    outputNumInput(&dgo->num, str_offs, &dgo->scene->unit);
+
+    BLI_snprintf(status_str, sizeof(status_str), "%s: %s", mode_str, str_offs);
+  }
+  else {
+    float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop);
+    BLI_snprintf(
+        status_str, sizeof(status_str), "%s: %d %%", mode_str, (int)(percentage * 100.0f));
+  }
+
+  ED_area_status_text(dgo->sa, status_str);
+}
+
+/* Calculate percentage based on position of mouse (we only use x-axis for now.
+ * Since this is more convenient for users to do), and store new percentage value.
+ */
+static void decimate_mouse_update_percentage(tDecimateGraphOp *dgo,
+                                             wmOperator *op,
+                                             const wmEvent *event)
+{
+  float percentage = (event->x - dgo->ar->winrct.xmin) / ((float)dgo->ar->winx);
+  RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage);
+}
+
+static int graphkeys_decimate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  tDecimateGraphOp *dgo;
+
+  WM_cursor_modal_set(CTX_wm_window(C), WM_CURSOR_EW_SCROLL);
+
+  /* Init slide-op data. */
+  dgo = op->customdata = MEM_callocN(sizeof(tDecimateGraphOp), "tDecimateGraphOp");
+
+  /* Get editor data. */
+  if (ANIM_animdata_get_context(C, &dgo->ac) == 0) {
+    decimate_exit(C, op);
+    return OPERATOR_CANCELLED;
+  }
+
+  dgo->percentage_prop = RNA_struct_find_property(op->ptr, "remove_ratio");
+
+  dgo->scene = CTX_data_scene(C);
+  dgo->sa = CTX_wm_area(C);
+  dgo->ar = CTX_wm_region(C);
+
+  /* initialise percentage so that it will have the correct value before the first mouse move. */
+  decimate_mouse_update_percentage(dgo, op, event);
+
+  decimate_draw_status_header(op, dgo);
+
+  /* Construct a list with the original bezt arrays so we can restore them during modal operation.
+   */
+  {
+    ListBase anim_data = {NULL, NULL};
+    bAnimContext *ac = &dgo->ac;
+    bAnimListElem *ale;
+
+    int filter;
+
+    /* filter data */
+    filter = (ANIMFILTER_DATA_VISIBLE | ANIMFILTER_CURVE_VISIBLE | ANIMFILTER_FOREDIT |
+              ANIMFILTER_SEL | ANIMFILTER_NODUPLIS);
+    ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype);
+
+    /* Loop through filtered data and copy the curves. */
+    for (ale = anim_data.first; ale; ale = ale->next) {
+      FCurve *fcu = (FCurve *)ale->key_data;
+      const int arr_size = sizeof(BezTriple) * fcu->totvert;
+
+      tBeztCopyData *copy = MEM_mallocN(sizeof(tBeztCopyData), "bezts_copy");
+      BezTriple *bezts_copy = MEM_mallocN(arr_size, "bezts_copy_array");
+
+      copy->tot_vert = fcu->totvert;
+      memcpy(bezts_copy, fcu->bezt, arr_size);
+
+      copy->bezt = bezts_copy;
+
+      LinkData *link = NULL;
+
+      link = MEM_callocN(sizeof(LinkData), "Bezt Link");
+      link->data = copy;
+
+      BLI_addtail(&dgo->bezt_arr_list, link);
+    }
+
+    ANIM_animdata_freelist(&anim_data);
+  }
+
+  WM_event_add_modal_handler(C, op);
+  return OPERATOR_RUNNING_MODAL;
+}
+
+static void graphkeys_decimate_modal_update(bContext *C, wmOperator *op)
+{
+  /* Perform decimate updates - in response to some user action
+   * (e.g. pressing a key or moving the mouse). */
+  tDecimateGraphOp *dgo = op->customdata;
+
+  decimate_draw_status_header(op, dgo);
+
+  /* Reset keyframe data (so we get back to the original state). */
+  decimate_reset_bezts(dgo);
+
+  /* apply... */
+  float remove_ratio = RNA_property_float_get(op->ptr, dgo->percentage_prop);
+  /* We don't want to limit the decimation to a certain error margin. */
+  const float error_sq_max = FLT_MAX;
+  decimate_graph_keys(&dgo->ac, remove_ratio, error_sq_max);
+  WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
+}
+
+static int graphkeys_decimate_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+  /* This assumes that we are in "DECIM_RATIO" mode. This is because the error margin is very hard
+   * and finicky to control with this modal mouse grab method. Therefore, it is expected that the
+   * error margin mode is not adjusted by the modal operator but instead tweaked via the redo
+   * panel.*/
+  tDecimateGraphOp *dgo = op->customdata;
+
+  const bool has_numinput = hasNumInput(&dgo->num);
+
+  switch (event->type) {
+    case LEFTMOUSE: /* confirm */
+    case RETKEY:
+    case PADENTER: {
+      if (event->val == KM_PRESS) {
+        ED_area_status_text(dgo->sa, NULL);
+
+        decimate_exit(C, op);
+
+        return OPERATOR_FINISHED;
+      }
+      break;
+    }
+
+    case ESCKEY: /* cancel */
+    case RIGHTMOUSE: {
+      if (event->val == KM_PRESS) {
+        /* Return to normal cursor and header status. */
+        ED_area_status_text(dgo->sa, NULL);
+
+        decimate_reset_bezts(dgo);
+
+        WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
+
+        decimate_exit(C, op);
+
+        return OPERATOR_CANCELLED;
+      }
+      break;
+    }
+
+    /* Percentage Change... */
+    case MOUSEMOVE: /* calculate new position */
+    {
+      if (has_numinput == false) {
+        /* Update percentage based on position of mouse. */
+        decimate_mouse_update_percentage(dgo, op, event);
+
+        /* Update pose to reflect the new values. */
+        graphkeys_decimate_modal_update(C, op);
+      }
+      break;
+    }
+    default: {
+      if ((event->val == KM_PRESS) && handleNumInput(C, &dgo->num, event)) {
+        float value;
+        float percentage = RNA_property_float_get(op->ptr, dgo->percentage_prop);
+
+        /* Grab percentage from numeric input, and store this new value for redo
+         * NOTE: users see ints, while internally we use a 0-1 float.
+         */
+        value = percentage * 100.0f;
+        applyNumInput(&dgo->num, &value);
+
+        percentage = value / 100.0f;
+        RNA_property_float_set(op->ptr, dgo->percentage_prop, percentage);
+
+        /* Update decimate output to reflect the new values. */
+        graphkeys_decimate_modal_update(C, op);
+        break;
+      }
+      else {
+        /* unhandled event - maybe it was some view manip? */
+        /* allow to pass through */
+        return OPERATOR_RUNNING_MODAL | OPERATOR_PASS_THROUGH;
+      }
+    }
+  }
+
+  return OPERATOR_RUNNING_MODAL;
+}
+
 static int graphkeys_decimate_exec(bContext *C, wmOperator *op)
 {
   bAnimContext ac;
-  float remove_ratio;
 
   /* get editor data */
   if (ANIM_animdata_get_context(C, &ac) == 0) {
     return OPERATOR_CANCELLED;
   }
 
-  remove_ratio = RNA_float_get(op->ptr, "remove_ratio");
-  decimate_graph_keys(&ac, remove_ratio);
+  tDecimModes mode = RNA_enum_get(op->ptr, "mode");
+  /* We want to be able to work on all available keyframes. */
+  float remove_ratio = 1.0f;
+  /* We don't want to limit the decimation to a certain error margin. */
+  float error_sq_max = FLT_MAX;
+
+  switch (mode) {
+    case DECIM_RATIO:
+      remove_ratio = RNA_float_get(op->ptr, "remove_ratio");
+      break;
+    case DECIM_ERROR:
+      error_sq_max = RNA_float_get(op->ptr, "remove_error_margin");
+      /* The decimate algorithm expects the error to be squared. */
+      error_sq_max *= error_sq_max;
+
+      break;
+  }
+
+  if (remove_ratio == 0.0f || error_sq_max == 0.0f) {
+    /* Nothing to remove. */
+    return OPERATOR_FINISHED;
+  }
+
+  decimate_graph_keys(&ac, remove_ratio, error_sq_max);
 
   /* set notifier that keyframes have changed */
   WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
@@ -1348,6 +1663,55 @@ static int graphkeys_decimate_exec(bContext *C, wmOperator *op)
   return OPERATOR_FINISHED;
 }
 
+static bool graphkeys_decimate_poll_property(const bContext *UNUSED(C),
+                                             wmOperator *op,
+                                             const PropertyRNA *prop)
+{
+  const char *prop_id = RNA_property_identifier(prop);
+
+  if (STRPREFIX(prop_id, "remove")) {
+    int mode = RNA_enum_get(op->ptr, "mode");
+
+    if (STREQ(prop_id, "remove_ratio") && mode != DECIM_RATIO) {
+      return false;
+    }
+    else if (STREQ(prop_id, "remove_error_margin") && mode != DECIM_ERROR) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+static char *graphkeys_decimate_desc(bContext *UNUSED(C),
+                                     wmOperatorType *UNUSED(op),
+                                     PointerRNA *ptr)
+{
+
+  if (RNA_enum_get(ptr, "mode") == DECIM_ERROR) {
+    return BLI_strdup(
+        "Decimate F-Curves by specifying how much it can deviate from the original curve");
+  }
+
+  /* Use default description. */
+  return NULL;
+}
+
+static const EnumPropertyItem decimate_mode_items[] = {
+    {DECIM_RATIO,
+     "RATIO",
+     0,
+     "Ratio",
+     "Use a percentage to specify how many keyframes you want to remove"},
+    {DECIM_ERROR,
+     "ERROR",
+     0,
+     "Error Margin",
+     "Use an error margin to specify how much the curve is allowed to deviate from the original "
+     "path"},
+    {0, NULL, 0, NULL, NULL},
+};
+
 void GRAPH_OT_decimate(wmOperatorType *ot)
 {
   /* identifiers */
@@ -1357,6 +1721,10 @@ void GRAPH_OT_decimate(wmOperatorType *ot)
       "Decimate F-Curves by removing keyframes that influence the curve shape the least";
 
   /* api callbacks */
+  ot->poll_property = graphkeys_decimate_poll_property;
+  ot->get_description = graphkeys_decimate_desc;
+  ot->invoke = graphkeys_decimate_invoke;
+  ot->modal = graphkeys_decimate_modal;
   ot->exec = graphkeys_decimate_exec;
   ot->poll = graphop_editable_keyframes_poll;
 
@@ -1364,15 +1732,31 @@ void GRAPH_OT_decimate(wmOperatorType *ot)
   ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 
   /* properties */
-  ot->prop = RNA_def_float_percentage(ot->srna,
-                                      "remove_ratio",
-                                      1.0f / 3.0f,
-                                      0.0f,
-                                      1.0f,
-                                      "Remove",
-                                      "The percentage of keyframes to remove",
-                                      0.0f,
-                                      1.0f);
+  RNA_def_enum(ot->srna,
+               "mode",
+               decimate_mode_items,
+               DECIM_RATIO,
+               "Mode",
+               "Which mode to use for decimation");
+
+  RNA_def_float_percentage(ot->srna,
+                           "remove_ratio",
+                           1.0f / 3.0f,
+                           0.0f,
+                           1.0f,
+                           "Remove",
+                           "The percentage of keyframes to remove",
+                           0.0f,
+                           1.0f);
+  RNA_def_float(ot->srna,
+                "remove_error_margin",
+                0.0f,
+                0.0f,
+                FLT_MAX,
+                "Max Error Margin",
+                "How much the new decimated curve is allowed to deviate from the original",
+                0.0f,
+                10.0f);
 }
 
 /* ******************** Bake F-Curve Operator *********************** */
index eb7ba480296f5a79fd943d5b7c6b123e221869d8..7bc907bb3db6d2ef839a00cdcaee4353c49cf7ac 100644 (file)
@@ -905,5 +905,8 @@ void ED_spacetype_ipo(void)
 
   graph_buttons_register(art);
 
+  art = ED_area_type_hud(st->spaceid);
+  BLI_addhead(&st->regiontypes, art);
+
   BKE_spacetype_register(st);
 }
index ec42e9bd04f50f4c5f321ffb6a2cd84c6b87ea0f..d3c5a707b44007b3394e9e0a8593ef883b52e01d 100644 (file)
@@ -382,6 +382,9 @@ typedef struct ARegion_Runtime {
    *
    * Lazy initialize, zero'd when unset, relative to #ARegion.winrct x/y min. */
   rcti visible_rect;
+
+  /* The offset needed to not overlap with window scrollbars. Only used by HUD regions for now. */
+  int offset_x, offset_y;
 } ARegion_Runtime;
 
 typedef struct ARegion {
index 89c6c05021e2094d40f567071093d60d3e42159e..7b0f3bf708c2dce1b35c9d245fec8adcc342b675 100644 (file)
@@ -4954,7 +4954,7 @@ static void rna_def_space_graph(BlenderRNA *brna)
   RNA_def_struct_sdna(srna, "SpaceGraph");
   RNA_def_struct_ui_text(srna, "Space Graph Editor", "Graph Editor space data");
 
-  rna_def_space_generic_show_region_toggles(srna, (1 << RGN_TYPE_UI));
+  rna_def_space_generic_show_region_toggles(srna, (1 << RGN_TYPE_UI) | (1 << RGN_TYPE_HUD));
 
   /* mode */
   prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);