Graph Editor: Euler Filter ported from Py to C
authorJoshua Leung <aligorith@gmail.com>
Fri, 25 Mar 2011 03:58:21 +0000 (03:58 +0000)
committerJoshua Leung <aligorith@gmail.com>
Fri, 25 Mar 2011 03:58:21 +0000 (03:58 +0000)
Ported joeedh's Euler Filter code from Python to C so that this is
more in line with the other Graph Editor tools - i.e. joeedh's version
only worked on the active bone's curves, while standard tools could
work with multiple bones/objects at the same time.

To use this new version of this operator:
1) Select all the F-Curves for all 3 of the components (XYZ) for the
euler rotations you wish to clean up. In the Graph Editor, they must
be one after the other (i.e. you can't have "RotX, RotY, something
else, RotZ")
2) Activate the operator from the Key menu in the Graph Editor

In an old test file I have floating around, this method did not appear
to be good enough to fix a very clear discontinuity in the middle of
the action, so I'll test some additional methods too

release/scripts/startup/bl_operators/__init__.py
release/scripts/startup/bl_operators/fcurve_euler_filter.py [deleted file]
source/blender/editors/space_graph/graph_edit.c
source/blender/editors/space_graph/graph_intern.h
source/blender/editors/space_graph/graph_ops.c

index 599cd6c1889716cd736ad509d65704bd128c9483..2a42cfbacb81b1123fb00ff0fc2860b0264128ad 100644 (file)
@@ -25,7 +25,6 @@ if "bpy" in locals():
 _modules = (
     "add_mesh_torus",
     "animsys_update",
-    "fcurve_euler_filter",
     "image",
     "mesh",
     "nla",
diff --git a/release/scripts/startup/bl_operators/fcurve_euler_filter.py b/release/scripts/startup/bl_operators/fcurve_euler_filter.py
deleted file mode 100644 (file)
index c7b249a..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-# ##### BEGIN GPL LICENSE BLOCK #####
-#
-#  This program is free software; you can redistribute it and/or
-#  modify it under the terms of the GNU General Public License
-#  as published by the Free Software Foundation; either version 2
-#  of the License, or (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU General Public License for more details.
-#
-#  You should have received a copy of the GNU General Public License
-#  along with this program; if not, write to the Free Software Foundation,
-#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-# <pep8 compliant>
-
-import bpy
-
-
-def main(context):
-    from math import pi
-
-    def cleanupEulCurve(fcv):
-        keys = []
-
-        for k in fcv.keyframe_points:
-            keys.append([k.handle_left.copy(), k.co.copy(), k.handle_right.copy()])
-
-        for i in range(len(keys)):
-            cur = keys[i]
-            prev = keys[i - 1] if i > 0 else None
-            next = keys[i + 1] if i < len(keys) - 1 else None
-
-            if prev is None:
-                continue
-
-            th = pi
-            if abs(prev[1][1] - cur[1][1]) >= th:  # more than 180 degree jump
-                fac = pi * 2.0
-                if prev[1][1] > cur[1][1]:
-                    while abs(cur[1][1] - prev[1][1]) >= th:  # < prev[1][1]:
-                        cur[0][1] += fac
-                        cur[1][1] += fac
-                        cur[2][1] += fac
-                elif prev[1][1] < cur[1][1]:
-                    while abs(cur[1][1] - prev[1][1]) >= th:
-                        cur[0][1] -= fac
-                        cur[1][1] -= fac
-                        cur[2][1] -= fac
-
-        for i in range(len(keys)):
-            for x in range(2):
-                fcv.keyframe_points[i].handle_left[x] = keys[i][0][x]
-                fcv.keyframe_points[i].co[x] = keys[i][1][x]
-                fcv.keyframe_points[i].handle_right[x] = keys[i][2][x]
-
-    flist = bpy.context.active_object.animation_data.action.fcurves
-    for f in flist:
-        if f.select and f.data_path.endswith("rotation_euler"):
-            cleanupEulCurve(f)
-
-
-class DiscontFilterOp(bpy.types.Operator):
-    """Fixes the most common causes of gimbal lock in the fcurves of the active bone"""
-    bl_idname = "graph.euler_filter"
-    bl_label = "Filter out discontinuities in the active fcurves"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-        main(context)
-        return {'FINISHED'}
index fdd43aa05664aaed86896463e4f0782989754052..deffc60019ee95dd7d9bc9970f13f86f6ab4b4ee 100644 (file)
@@ -1501,12 +1501,13 @@ static int graphkeys_handletype_exec(bContext *C, wmOperator *op)
  * of values to -180 degrees to 180 degrees.
  */
 
-#if 0 // XXX this is not ready for the primetime yet
 /* set of three euler-rotation F-Curves */
 typedef struct tEulerFilter {
+       struct tEulerFilter *next, *prev;
+       
        ID *id;                                                 /* ID-block which owns the channels */
-       FCurve (*fcurves)[3];                   /* 3 Pointers to F-Curves */                            
+       FCurve *(fcurves[3]);                   /* 3 Pointers to F-Curves */    
+       char *rna_path;                                 /* Pointer to one of the RNA Path's used by one of the F-Curves */
 } tEulerFilter;
  
 static int graphkeys_euler_filter_exec (bContext *C, wmOperator *op)
@@ -1518,7 +1519,8 @@ static int graphkeys_euler_filter_exec (bContext *C, wmOperator *op)
        int filter;
        
        ListBase eulers = {NULL, NULL};
-       tEulerFilter *euf= NULL;        
+       tEulerFilter *euf= NULL;
+       int groups=0, failed=0;
        
        /* get editor data */
        if (ANIM_animdata_get_context(C, &ac) == 0)
@@ -1528,7 +1530,7 @@ static int graphkeys_euler_filter_exec (bContext *C, wmOperator *op)
         *       1) Sets of three related rotation curves are identified from the selected channels,
         *              and are stored as a single 'operation unit' for the next step
         *       2) Each set of three F-Curves is processed for each keyframe, with the values being
-        *              processed according to one of several ways.
+        *              processed as necessary
         */
         
        /* step 1: extract only the rotation f-curves */
@@ -1542,45 +1544,134 @@ static int graphkeys_euler_filter_exec (bContext *C, wmOperator *op)
                 *      - only rotation curves
                 *      - for pchan curves, make sure we're only using the euler curves
                 */
-               if (strstr(fcu->rna_path, "rotation_euler") == 0)
+               if (strstr(fcu->rna_path, "rotation_euler") == NULL)
                        continue;
+               else if (ELEM3(fcu->array_index, 0, 1, 2) == 0) {
+                       BKE_reportf(op->reports, RPT_WARNING,
+                               "Euler Rotation F-Curve has invalid index (ID='%s', Path='%s', Index=%d)", 
+                               (ale->id)? ale->id->name:"<No ID>", fcu->rna_path, fcu->array_index);
+                       continue;
+               }
                
-               /* check if current set of 3-curves is suitable to add this curve to 
-                *      - things like whether the current set of curves is 'full' should be checked later only
-                *      - first check if id-blocks are compatible
+               /* optimisation: assume that xyz curves will always be stored consecutively,
+                * so if the paths or the ID's don't match up, then a curve needs to be added 
+                * to a new group
                 */
-               if ((euf) && (ale->id != euf->id)) {
-                       /* if the paths match, add this curve to the set of curves */
-                       // NOTE: simple string compare for now... could be a bit more fancy...
-                       
+               if ((euf) && (euf->id == ale->id) && (strcmp(euf->rna_path, fcu->rna_path)==0)) {
+                       /* this should be fine to add to the existing group then */
+                       euf->fcurves[fcu->array_index]= fcu;
                }
                else {
                        /* just add to a new block */
                        euf= MEM_callocN(sizeof(tEulerFilter), "tEulerFilter");
                        BLI_addtail(&eulers, euf);
+                       groups++;
                        
                        euf->id= ale->id;
+                       euf->rna_path = fcu->rna_path; /* this should be safe, since we're only using it for a short time */
                        euf->fcurves[fcu->array_index]= fcu;
                }
        }
        BLI_freelistN(&anim_data);
        
+       if (groups == 0) {
+               BKE_report(op->reports, RPT_WARNING, "No Euler Rotation F-Curves to fix up");
+               return OPERATOR_CANCELLED;
+       }
+       
        /* step 2: go through each set of curves, processing the values at each keyframe 
         *      - it is assumed that there must be a full set of keyframes at each keyframe position
         */
        for (euf= eulers.first; euf; euf= euf->next) {
+               int f;
+               
+               /* sanity check: ensure that there are enough F-Curves to work on in this group */
+               // TODO: also enforce assumption that there be a full set of keyframes at each position by ensuring that totvert counts are same?
+               if (ELEM3(NULL, euf->fcurves[0], euf->fcurves[1], euf->fcurves[2])) {
+                       /* report which components are missing */
+                       BKE_reportf(op->reports, RPT_WARNING,
+                               "Missing %s%s%s component(s) of euler rotation for ID='%s' and RNA-Path='%s'",
+                               (euf->fcurves[0]==NULL)? "X":"",
+                               (euf->fcurves[1]==NULL)? "Y":"",
+                               (euf->fcurves[2]==NULL)? "Z":"",
+                               euf->id->name, euf->rna_path);
+                               
+                       /* keep track of number of failed sets, and carry on to next group */
+                       failed++;
+                       continue;
+               }
                
+               /* simple method: just treat any difference between keys of greater than 180 degrees as being a flip */
+               // FIXME: there are more complicated methods that will be needed to fix more cases than just some
+               for (f = 0; f < 3; f++) {
+                       FCurve *fcu = euf->fcurves[f];
+                       BezTriple *bezt, *prev=NULL;
+                       unsigned int i;
+                       
+                       /* skip if not enough vets to do a decent analysis of... */
+                       if (fcu->totvert <= 2)
+                               continue;
+                       
+                       /* prev follows bezt, bezt = "current" point to be fixed */
+                       for (i=0, bezt=fcu->bezt; i < fcu->totvert; i++, prev=bezt, bezt++) {
+                               /* our method depends on determining a "difference" from the previous vert */
+                               if (prev == NULL)
+                                       continue;
+                               
+                               /* > 180 degree flip? */
+                               if (fabs(prev->vec[1][1] - bezt->vec[1][1]) >= M_PI) {
+                                       /* 360 degrees to add/subtract frame value until difference is acceptably small that there's no more flip */
+                                       const double fac = 2.0 * M_PI;
+                                       
+                                       if (prev->vec[1][1] > bezt->vec[1][1]) {
+                                               while (fabs(bezt->vec[1][1] - prev->vec[1][1]) >= M_PI) {
+                                                       bezt->vec[0][1] += fac;
+                                                       bezt->vec[1][1] += fac;
+                                                       bezt->vec[2][1] += fac;
+                                               }
+                                       }
+                                       else /* if (prev->vec[1][1] < bezt->vec[1][1]) */ {
+                                               while (fabs(bezt->vec[1][1] - prev->vec[1][1]) >= M_PI) {
+                                                       bezt->vec[0][1] -= fac;
+                                                       bezt->vec[1][1] -= fac;
+                                                       bezt->vec[2][1] -= fac;
+                                               }
+                                       }
+                               }
+                       }
+               }
        }
        BLI_freelistN(&eulers);
        
-       return OPERATOR_FINISHED;
+       /* updates + finishing warnings */
+       if (failed == groups) {
+               BKE_report(op->reports, RPT_ERROR, 
+                       "No Euler Rotations could be corrected. Ensure each rotation has keys for all components, and that F-Curves for these are in consecutive XYZ order and selected.");
+               return OPERATOR_CANCELLED;
+       }
+       else {
+               if (failed) {
+                       BKE_report(op->reports, RPT_ERROR,
+                               "Some Euler Rotations couldn't be corrected due to missing/unselected/out-of-order F-Curves. Ensure each rotation has keys for all components, and that F-Curves for these are in consecutive XYZ order and selected.");
+               }
+               
+               /* validate keyframes after editing */
+               ANIM_editkeyframes_refresh(&ac);
+               
+               /* set notifier that keyframes have changed */
+               WM_event_add_notifier(C, NC_ANIMATION|ND_KEYFRAME|NA_EDITED, NULL);
+               
+               /* done at last */
+               return OPERATOR_FINISHED;
+       }
 }
  
 void GRAPH_OT_euler_filter (wmOperatorType *ot)
 {
        /* identifiers */
-       ot->name= "Euler Filter";
+       ot->name= "Euler Discontinuity Filter";
        ot->idname= "GRAPH_OT_euler_filter";
+       ot->description= "Fixes the most common causes of gimbal lock in the selected Euler Rotation F-Curves";
        
        /* api callbacks */
        ot->exec= graphkeys_euler_filter_exec;
@@ -1590,8 +1681,6 @@ void GRAPH_OT_euler_filter (wmOperatorType *ot)
        ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
 }
 
-#endif // XXX this is not ready for the primetime yet
-
 /* ***************** Jump to Selected Frames Operator *********************** */
 
 /* snap current-frame indicator to 'average time' of selected keyframe */
index feb17827db78458eb0d789cab576ea331f107ffa..2aefb7c64dd23851528b2e39bbd67425bbaf9c39 100644 (file)
@@ -111,6 +111,7 @@ void GRAPH_OT_sample(struct wmOperatorType *ot);
 void GRAPH_OT_bake(struct wmOperatorType *ot);
 void GRAPH_OT_sound_bake(struct wmOperatorType *ot);
 void GRAPH_OT_smooth(struct wmOperatorType *ot);
+void GRAPH_OT_euler_filter(struct wmOperatorType *ot);
 
 void GRAPH_OT_handle_type(struct wmOperatorType *ot);
 void GRAPH_OT_interpolation_type(struct wmOperatorType *ot);
index 674d28091887f86a689e345fbfbef258987f06d9..e75fbeb8efc48f6da4d12dc30378c31a3883f44c 100644 (file)
@@ -255,6 +255,7 @@ void graphedit_operatortypes(void)
        WM_operatortype_append(GRAPH_OT_sound_bake);
        WM_operatortype_append(GRAPH_OT_smooth);
        WM_operatortype_append(GRAPH_OT_clean);
+       WM_operatortype_append(GRAPH_OT_euler_filter);
        WM_operatortype_append(GRAPH_OT_delete);
        WM_operatortype_append(GRAPH_OT_duplicate);