[Bf-blender-cvs] [3ceff6863ea] temp-angavrilov: Animation: support filtering for curves that don't match the action cycle.

Alexander Gavrilov noreply at git.blender.org
Thu Oct 21 20:41:00 CEST 2021


Commit: 3ceff6863ea02f9007d4f5b1f18a9a7d6df736a3
Author: Alexander Gavrilov
Date:   Mon May 3 17:27:53 2021 +0300
Branches: temp-angavrilov
https://developer.blender.org/rB3ceff6863ea02f9007d4f5b1f18a9a7d6df736a3

Animation: support filtering for curves that don't match the action cycle.

Since the looping behavior is defined per curve rather than at
action level, it is possible for curve loop periods to get out of
sync with each other. This commit adds an option to compare curves
against the frame range specified in the action, and treat any
mismatches as errors for the purpose of F-Curve filtering.

When enabled, the check verifies that curves within the action
have valid cyclic extrapolation, the action period evenly
divides by the curve period (since a curve looping at e.g. half
of the action period length still repeats in sync with the action),
and the end values match if the cycle is supposed to be perfect.

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

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

M	release/scripts/startup/bl_ui/space_dopesheet.py
M	source/blender/blenkernel/BKE_fcurve.h
M	source/blender/blenkernel/intern/fcurve.c
M	source/blender/editors/animation/anim_filter.c
M	source/blender/makesdna/DNA_action_types.h
M	source/blender/makesrna/intern/rna_action.c

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

diff --git a/release/scripts/startup/bl_ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py
index aac24ed44d5..f0dac42e4d5 100644
--- a/release/scripts/startup/bl_ui/space_dopesheet.py
+++ b/release/scripts/startup/bl_ui/space_dopesheet.py
@@ -715,6 +715,10 @@ class DOPESHEET_PT_action(Panel):
 
         col.prop(action, "use_cyclic")
 
+        row = col.row()
+        row.active = action.use_cyclic
+        row.prop(action, "use_cyclic_errors")
+
 
 class LayersDopeSheetPanel:
     bl_space_type = 'DOPESHEET_EDITOR'
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h
index f494c2e30cc..96a14bd744a 100644
--- a/source/blender/blenkernel/BKE_fcurve.h
+++ b/source/blender/blenkernel/BKE_fcurve.h
@@ -294,7 +294,7 @@ typedef enum eFCU_Cycle_Type {
   FCU_CYCLE_OFFSET,
 } eFCU_Cycle_Type;
 
-eFCU_Cycle_Type BKE_fcurve_get_cycle_type(struct FCurve *fcu);
+eFCU_Cycle_Type BKE_fcurve_get_cycle_type(const struct FCurve *fcu);
 
 /* Recompute handles to neatly subdivide the prev-next range at bezt. */
 bool BKE_fcurve_bezt_subdivide_handles(struct BezTriple *bezt,
diff --git a/source/blender/blenkernel/intern/fcurve.c b/source/blender/blenkernel/intern/fcurve.c
index 8e9c504dcbf..a8635fffcd3 100644
--- a/source/blender/blenkernel/intern/fcurve.c
+++ b/source/blender/blenkernel/intern/fcurve.c
@@ -1232,7 +1232,7 @@ void fcurve_samples_to_keyframes(FCurve *fcu, const int start, const int end)
  */
 
 /* Checks if the F-Curve has a Cycles modifier, and returns the type of the cycle behavior. */
-eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu)
+eFCU_Cycle_Type BKE_fcurve_get_cycle_type(const FCurve *fcu)
 {
   FModifier *fcm = fcu->modifiers.first;
 
diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c
index e1d046428a8..7872e20c618 100644
--- a/source/blender/editors/animation/anim_filter.c
+++ b/source/blender/editors/animation/anim_filter.c
@@ -79,6 +79,7 @@
 #include "BLI_alloca.h"
 #include "BLI_blenlib.h"
 #include "BLI_ghash.h"
+#include "BLI_math.h"
 #include "BLI_string.h"
 #include "BLI_utildefines.h"
 
@@ -1225,12 +1226,56 @@ static bool skip_fcurve_with_name(
   return true;
 }
 
+/* Check if the F-Curve doesn't cycle properly based on action settings. */
+static bool fcurve_has_cyclic_errors(const FCurve *fcu, const bAction *act)
+{
+  /* Check if the curve has enough points. */
+  if (fcu->totvert < 2 || !fcu->bezt) {
+    return true;
+  }
+
+  /* Check if it is cyclic. */
+  const eFCU_Cycle_Type cycle_type = BKE_fcurve_get_cycle_type(fcu);
+
+  if (cycle_type == FCU_CYCLE_NONE) {
+    return true;
+  }
+
+  /* Check that it has a nonzero period length. */
+  const BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert - 1];
+
+  const float curve_period = last->vec[1][0] - first->vec[1][0];
+
+  if (curve_period < 0.1f) {
+    return true;
+  }
+
+  /* Check that the action period is divisible by the curve period. */
+  const float action_period = act->frame_end - act->frame_start;
+  const float gap = action_period - roundf(action_period / curve_period) * curve_period;
+
+  if (fabsf(gap) > 1e-3f) {
+    return true;
+  }
+
+  /* Check that the start and end values match in a perfect cycle. */
+  if (cycle_type == FCU_CYCLE_PERFECT) {
+    const float magnitude = max_fff(fabsf(first->vec[1][1]), fabsf(last->vec[1][1]), 0.01f);
+
+    if (fabsf(first->vec[1][1] - last->vec[1][1]) > magnitude * 1e-4f) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 /**
  * Check if F-Curve has errors and/or is disabled
  *
  * \return true if F-Curve has errors/is disabled
  */
-static bool fcurve_has_errors(const FCurve *fcu)
+static bool fcurve_has_errors(const FCurve *fcu, const bAction *act)
 {
   /* F-Curve disabled - path eval error */
   if (fcu->flag & FCURVE_DISABLED) {
@@ -1262,6 +1307,12 @@ static bool fcurve_has_errors(const FCurve *fcu)
     }
   }
 
+  /* Check cycle errors. */
+  if (BKE_action_is_cyclic(act) && (act->flag & ACT_CYCLIC_ERRORS) &&
+      fcurve_has_cyclic_errors(fcu, act)) {
+    return true;
+  }
+
   /* no errors found */
   return false;
 }
@@ -1271,6 +1322,7 @@ static FCurve *animfilter_fcurve_next(bDopeSheet *ads,
                                       FCurve *first,
                                       eAnim_ChannelType channel_type,
                                       int filter_mode,
+                                      bAction *act,
                                       void *owner,
                                       ID *owner_id)
 {
@@ -1322,7 +1374,7 @@ static FCurve *animfilter_fcurve_next(bDopeSheet *ads,
             /* error-based filtering... */
             if ((ads) && (ads->filterflag & ADS_FILTER_ONLY_ERRORS)) {
               /* skip if no errors... */
-              if (fcurve_has_errors(fcu) == false) {
+              if (fcurve_has_errors(fcu, act) == false) {
                 continue;
               }
             }
@@ -1344,6 +1396,7 @@ static size_t animfilter_fcurves(ListBase *anim_data,
                                  FCurve *first,
                                  eAnim_ChannelType fcurve_type,
                                  int filter_mode,
+                                 bAction *act,
                                  void *owner,
                                  ID *owner_id,
                                  ID *fcurve_owner_id)
@@ -1364,7 +1417,7 @@ static size_t animfilter_fcurves(ListBase *anim_data,
    *    Back to step 2 :)
    */
   for (fcu = first;
-       ((fcu = animfilter_fcurve_next(ads, fcu, fcurve_type, filter_mode, owner, owner_id)));
+       ((fcu = animfilter_fcurve_next(ads, fcu, fcurve_type, filter_mode, act, owner, owner_id)));
        fcu = fcu->next) {
     if (UNLIKELY(fcurve_type == ANIMTYPE_NLACURVE)) {
       /* NLA Control Curve - Basically the same as normal F-Curves,
@@ -1439,11 +1492,18 @@ static size_t animfilter_act_group(bAnimContext *ac,
         if (!(filter_mode & ANIMFILTER_FOREDIT) || EDITABLE_AGRP(agrp)) {
           /* get first F-Curve which can be used here */
           FCurve *first_fcu = animfilter_fcurve_next(
-              ads, agrp->channels.first, ANIMTYPE_FCURVE, filter_mode, agrp, owner_id);
+              ads, agrp->channels.first, ANIMTYPE_FCURVE, filter_mode, act, agrp, owner_id);
 
           /* filter list, starting from this F-Curve */
-          tmp_items += animfilter_fcurves(
-              &tmp_data, ads, first_fcu, ANIMTYPE_FCURVE, filter_mode, agrp, owner_id, &act->id);
+          tmp_items += animfilter_fcurves(&tmp_data,
+                                          ads,
+                                          first_fcu,
+                                          ANIMTYPE_FCURVE,
+                                          filter_mode,
+                                          act,
+                                          agrp,
+                                          owner_id,
+                                          &act->id);
         }
       }
     }
@@ -1508,7 +1568,7 @@ static size_t animfilter_action(bAnimContext *ac,
   if (!(filter_mode & ANIMFILTER_ACTGROUPED)) {
     FCurve *firstfcu = (lastchan) ? (lastchan->next) : (act->curves.first);
     items += animfilter_fcurves(
-        anim_data, ads, firstfcu, ANIMTYPE_FCURVE, filter_mode, NULL, owner_id, &act->id);
+        anim_data, ads, firstfcu, ANIMTYPE_FCURVE, filter_mode, act, NULL, owner_id, &act->id);
   }
 
   /* return the number of items added to the list */
@@ -1641,6 +1701,7 @@ static size_t animfilter_nla_controls(
                                         strip->fcurves.first,
                                         ANIMTYPE_NLACURVE,
                                         filter_mode,
+                                        NULL,
                                         strip,
                                         owner_id,
                                         owner_id);
@@ -1697,8 +1758,15 @@ static size_t animfilter_block_data(
           items += animfilter_nla(ac, anim_data, ads, adt, filter_mode, id);
         },
         { /* Drivers */
-          items += animfilter_fcurves(
-              anim_data, ads, adt->drivers.first, ANIMTYPE_FCURVE, filter_mode, NULL, id, id);
+          items += animfilter_fcurves(anim_data,
+                                      ads,
+                                      adt->drivers.first,
+                                      ANIMTYPE_FCURVE,
+                                      filter_mode,
+                                      NULL,
+                                      NULL,
+                                      id,
+                                      id);
         },
         { /* NLA Control Keyframes */
           items += animfilter_nla_controls(anim_data, ads, adt, filter_mode, id);
diff --git a/source/blender/makesdna/DNA_action_types.h b/source/blender/makesdna/DNA_action_types.h
index 8e7551e1703..61e1f7ffc0f 100644
--- a/source/blender/makesdna/DNA_action_types.h
+++ b/source/blender/makesdna/DNA_action_types.h
@@ -703,6 +703,8 @@ typedef enum eAction_Flags {
   ACT_FRAME_RANGE = (1 << 12),
   /** The action is intended to be a cycle (requires ACT_FRAME_RANGE). */
   ACT_CYCLIC = (1 << 13),
+  /** Treat cycle length or extrapolation mismatch as a curve error. */
+  ACT_CYCLIC_ERRORS = (1 << 14),
 } eAction_Flags;
 
 /* ************************************************ */
diff --git a/source/blender/makesrna/intern/rna_action.c b/source/blender/makesrna/intern/rna_action.c
index 96e37dfebbb..7fb067ac9ba 100644
--- a/source/blender/makesrna/intern/rna_action.c
+++ b/source/blender/makesrna/intern/rna_action.c
@@ -903,6 +903,15 @@ static void rna_def_action(BlenderRNA *brna)
       "playback frame range (enabling this doesn't automatically make it loop)");
   RNA_def_property_update(prop, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL);
 
+  prop = RNA_def_property(srna, "use_cyclic_errors", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", ACT_CYCLIC_ERRORS);
+  RNA_def_property_ui_text(prop,
+                           "Detect Cycle Err

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list