KeyingSets: First working prototype
authorJoshua Leung <aligorith@gmail.com>
Thu, 12 Feb 2009 10:41:57 +0000 (10:41 +0000)
committerJoshua Leung <aligorith@gmail.com>
Thu, 12 Feb 2009 10:41:57 +0000 (10:41 +0000)
To use KeyingSets, simply Outliner-select items in the Datablocks view and press K to add to the active KeyingSet. Then keyframes can be inserted by choosing the 'Active Keying Set' option when inserting keyframes.

Important notes on the current implementation:
* Only properties directly inside some ID-block that is close to the root (i.e. main -> objects -> "someobj" -> location, or main -> materials -> "somemat" -> colour) can be accessed for now, as I haven't got the code for building the inner-parts of the paths working yet. Help on getting this working is welcome (hint to Brecht).
* Properties that can be safely included include simple properties "object -> Dupli Verts", entire arrays "object -> Location" or individual array elements "object -> Location -> y"

---

Also added typo fix for KeyingSet freeing. It was freeing the KeyingSet instead of it's paths.

source/blender/blenkernel/intern/anim_sys.c
source/blender/editors/animation/keyframing.c
source/blender/editors/space_graph/graph_draw.c
source/blender/editors/space_outliner/outliner.c

index 55597b635c9b19becde288efbb92cd52aab19d9f..098d0ad7a32e4db2ae24e2cec7e5c3d4ade8b782 100644 (file)
@@ -248,7 +248,7 @@ void BKE_keyingset_free (KeyingSet *ks)
                MEM_freeN(ksp->rna_path);
                
                /* free path itself */
-               BLI_freelinkN(&ks->paths, ks);
+               BLI_freelinkN(&ks->paths, ksp);
        }
 }
 
index 08ee83277a8610881138fba7a3cfa3c169d87231..157609ed64055b08b43f774881db12d30b951a3f 100644 (file)
@@ -111,7 +111,7 @@ FCurve *verify_fcurve (ID *id, const char group[], const char rna_path[], const
                /* use default settings to make a F-Curve */
                fcu= MEM_callocN(sizeof(FCurve), "FCurve");
                
-               fcu->flag |= (FCURVE_VISIBLE|FCURVE_AUTO_HANDLES);
+               fcu->flag = (FCURVE_VISIBLE|FCURVE_AUTO_HANDLES|FCURVE_SELECTED);
                if (act->curves.first==NULL) 
                        fcu->flag |= FCURVE_ACTIVE;     /* first one added active */
                        
@@ -119,6 +119,9 @@ FCurve *verify_fcurve (ID *id, const char group[], const char rna_path[], const
                fcu->rna_path= BLI_strdupn(rna_path, strlen(rna_path));
                fcu->array_index= array_index;
                
+               /* set additional flags */
+               // TODO: need to set the FCURVE_INT_VALUES flag must be set if property is not float!
+               
                
                /* if a group name has been provided, try to add or find a group, then add F-Curve to it */
                if (group) {
@@ -2019,13 +2022,14 @@ static int commonkey_modifykey (ListBase *dsources, KeyingSet *ks, short mode, f
        else if (mode == COMMONKEY_MODE_DELETE)
                kflag= 0;
        
-       
        /* check if the KeyingSet is absolute or not (i.e. does it requires sources info) */
        if (ks->flag & KEYINGSET_ABSOLUTE) {
                /* Absolute KeyingSets are simpler to use, as all the destination info has already been
                 * provided by the user, and is stored, ready to use, in the KeyingSet paths.
                 */
                for (ksp= ks->paths.first; ksp; ksp= ksp->next) {
+                       int arraylen, i;
+                       
                        /* get pointer to name of group to add channels to */
                        if (ksp->flag & KSP_FLAG_GROUP_NONE)
                                groupname= NULL;
@@ -2034,11 +2038,34 @@ static int commonkey_modifykey (ListBase *dsources, KeyingSet *ks, short mode, f
                        else
                                groupname= ksp->group;
                        
-                       /* action to take depends on mode */
-                       if (mode == COMMONKEY_MODE_INSERT)
-                               success+= insertkey(ksp->id, groupname, ksp->rna_path, ksp->array_index, cfra, kflag);
-                       else if (mode == COMMONKEY_MODE_DELETE)
-                               success+= deletekey(ksp->id, groupname,  ksp->rna_path, ksp->array_index, cfra, kflag);
+                       /* init arraylen and i - arraylen should be greater than i so that
+                        * normal non-array entries get keyframed correctly
+                        */
+                       i= ksp->array_index;
+                       arraylen= i+1;
+                       
+                       /* get length of array if whole array option is enabled */
+                       if (ksp->flag & KSP_FLAG_WHOLE_ARRAY) {
+                               PointerRNA id_ptr, ptr;
+                               PropertyRNA *prop;
+                               
+                               RNA_id_pointer_create(ksp->id, &id_ptr);
+                               if (RNA_path_resolve(&id_ptr, ksp->rna_path, &ptr, &prop))
+                                       arraylen= RNA_property_array_length(&ptr, prop);
+                       }
+                       
+                       /* for each possible index, perform operation 
+                        *      - assume that arraylen is greater than index
+                        */
+                       for (; i < arraylen; i++) {
+                               /* action to take depends on mode */
+                               if (mode == COMMONKEY_MODE_INSERT)
+                                       success+= insertkey(ksp->id, groupname, ksp->rna_path, i, cfra, kflag);
+                               else if (mode == COMMONKEY_MODE_DELETE)
+                                       success+= deletekey(ksp->id, groupname,  ksp->rna_path, i, cfra, kflag);
+                       }
+                       
+                       // TODO: set recalc tags on the ID?
                }
        }
 #if 0 // XXX still need to figure out how to get such keyingsets working
@@ -2148,96 +2175,97 @@ static int insert_key_exec (bContext *C, wmOperator *op)
                else
                        return OPERATOR_FINISHED;
        }
-       
-       // XXX more comprehensive tests will be needed
-       CTX_DATA_BEGIN(C, Base*, base, selected_bases) 
-       {
-               Object *ob= base->object;
-               ID *id= (ID *)ob;
-               short success= 0;
-               
-               /* check which keyframing mode chosen for this object */
-               if (mode < 4) {
-                       /* object-based keyframes */
-                       switch (mode) {
-                       case 4: /* color of active material (only for geometry...) */
-                               // NOTE: this is just a demo... but ideally we'd go through materials instead of active one only so reference stays same
-                               // XXX no group for now
-                               success+= insertkey(id, NULL, "active_material.diffuse_color", 0, cfra, 0);
-                               success+= insertkey(id, NULL, "active_material.diffuse_color", 1, cfra, 0);
-                               success+= insertkey(id, NULL, "active_material.diffuse_color", 2, cfra, 0);
-                               break;
-                       case 3: /* object scale */
-                               success+= insertkey(id, "Object Transforms", "scale", 0, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "scale", 1, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "scale", 2, cfra, 0);
-                               break;
-                       case 2: /* object rotation */
-                               success+= insertkey(id, "Object Transforms", "rotation", 0, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "rotation", 1, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "rotation", 2, cfra, 0);
-                               break;
-                       case 1: /* object location */
-                               success+= insertkey(id, "Object Transforms", "location", 0, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "location", 1, cfra, 0);
-                               success+= insertkey(id, "Object Transforms", "location", 2, cfra, 0);
-                               break;
-                       }
+       else {
+               // more comprehensive tests will be needed
+               CTX_DATA_BEGIN(C, Base*, base, selected_bases) 
+               {
+                       Object *ob= base->object;
+                       ID *id= (ID *)ob;
+                       short success= 0;
                        
-                       ob->recalc |= OB_RECALC_OB;
-               }
-               else if ((ob->pose) && (ob->flag & OB_POSEMODE)) {
-                       /* PoseChannel based keyframes */
-                       bPoseChannel *pchan;
-                       
-                       for (pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
-                               /* only if selected */
-                               if ((pchan->bone) && (pchan->bone->flag & BONE_SELECTED)) {
-                                       char buf[512];
-                                       
-                                       switch (mode) {
-                                       case 6: /* pchan scale */
-                                               sprintf(buf, "pose.pose_channels[\"%s\"].scale", pchan->name);
-                                               success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
-                                               success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
-                                               success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
-                                               break;
-                                       case 5: /* pchan rotation */
-                                               if (pchan->rotmode == PCHAN_ROT_QUAT) {
-                                                       sprintf(buf, "pose.pose_channels[\"%s\"].rotation", pchan->name);
+                       /* check which keyframing mode chosen for this object */
+                       if (mode < 4) {
+                               /* object-based keyframes */
+                               switch (mode) {
+                               case 4: /* color of active material (only for geometry...) */
+                                       // NOTE: this is just a demo... but ideally we'd go through materials instead of active one only so reference stays same
+                                       // XXX no group for now
+                                       success+= insertkey(id, NULL, "active_material.diffuse_color", 0, cfra, 0);
+                                       success+= insertkey(id, NULL, "active_material.diffuse_color", 1, cfra, 0);
+                                       success+= insertkey(id, NULL, "active_material.diffuse_color", 2, cfra, 0);
+                                       break;
+                               case 3: /* object scale */
+                                       success+= insertkey(id, "Object Transforms", "scale", 0, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "scale", 1, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "scale", 2, cfra, 0);
+                                       break;
+                               case 2: /* object rotation */
+                                       success+= insertkey(id, "Object Transforms", "rotation", 0, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "rotation", 1, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "rotation", 2, cfra, 0);
+                                       break;
+                               case 1: /* object location */
+                                       success+= insertkey(id, "Object Transforms", "location", 0, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "location", 1, cfra, 0);
+                                       success+= insertkey(id, "Object Transforms", "location", 2, cfra, 0);
+                                       break;
+                               }
+                               
+                               ob->recalc |= OB_RECALC_OB;
+                       }
+                       else if ((ob->pose) && (ob->flag & OB_POSEMODE)) {
+                               /* PoseChannel based keyframes */
+                               bPoseChannel *pchan;
+                               
+                               for (pchan= ob->pose->chanbase.first; pchan; pchan= pchan->next) {
+                                       /* only if selected */
+                                       if ((pchan->bone) && (pchan->bone->flag & BONE_SELECTED)) {
+                                               char buf[512];
+                                               
+                                               switch (mode) {
+                                               case 6: /* pchan scale */
+                                                       sprintf(buf, "pose.pose_channels[\"%s\"].scale", pchan->name);
                                                        success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
                                                        success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
                                                        success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
-                                                       success+= insertkey(id, pchan->name, buf, 3, cfra, 0);
-                                               }
-                                               else {
-                                                       sprintf(buf, "pose.pose_channels[\"%s\"].euler_rotation", pchan->name);
+                                                       break;
+                                               case 5: /* pchan rotation */
+                                                       if (pchan->rotmode == PCHAN_ROT_QUAT) {
+                                                               sprintf(buf, "pose.pose_channels[\"%s\"].rotation", pchan->name);
+                                                               success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
+                                                               success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
+                                                               success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
+                                                               success+= insertkey(id, pchan->name, buf, 3, cfra, 0);
+                                                       }
+                                                       else {
+                                                               sprintf(buf, "pose.pose_channels[\"%s\"].euler_rotation", pchan->name);
+                                                               success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
+                                                               success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
+                                                               success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
+                                                       }
+                                                       break;
+                                               default: /* pchan location */
+                                                       sprintf(buf, "pose.pose_channels[\"%s\"].location", pchan->name);
                                                        success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
                                                        success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
                                                        success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
+                                                       break;
                                                }
-                                               break;
-                                       default: /* pchan location */
-                                               sprintf(buf, "pose.pose_channels[\"%s\"].location", pchan->name);
-                                               success+= insertkey(id, pchan->name, buf, 0, cfra, 0);
-                                               success+= insertkey(id, pchan->name, buf, 1, cfra, 0);
-                                               success+= insertkey(id, pchan->name, buf, 2, cfra, 0);
-                                               break;
                                        }
                                }
+                               
+                               ob->recalc |= OB_RECALC_OB;
                        }
                        
-                       ob->recalc |= OB_RECALC_OB;
+                       printf("Ob '%s' - Successfully added %d Keyframes \n", id->name+2, success);
                }
-               
-               printf("Ob '%s' - Successfully added %d Keyframes \n", id->name+2, success);
+               CTX_DATA_END;
        }
-       CTX_DATA_END;
        
        /* send updates */
        ED_anim_dag_flush_update(C);    
        
-       if (mode == 3) // material color requires different notifiers
+       if (mode == 4) // material color requires different notifiers
                WM_event_add_notifier(C, NC_MATERIAL|ND_KEYS, NULL);
        else
                WM_event_add_notifier(C, NC_OBJECT|ND_KEYS, NULL);
index 077706f0c2b79054308608afa6abe3633448a45f..86316d9321415d819175d18ae16c5cbff679636f 100644 (file)
@@ -708,7 +708,7 @@ void graph_draw_curves (bAnimContext *ac, SpaceIpo *sipo, ARegion *ar)
                /* draw curve - we currently calculate colour on the fly, but that should probably be done in advance instead */
                if ( ((fcu->bezt) || (fcu->fpt)) && (fcu->totvert) ) { 
                        /* set color/drawing style for curve itself */
-                       if (fcu->flag & FCURVE_PROTECTED) {
+                       if ( ((fcu->grp) && (fcu->grp->flag & AGRP_PROTECTED)) || (fcu->flag & FCURVE_PROTECTED) ) {
                                /* protected curves (non editable) are drawn with dotted lines */
                                setlinestyle(2);
                        }
index 2d3ccedd20118e1f6164a420dcfcbf52c4783540..e91d478d327f5c8e39b5b48a74857ad8265673a2 100644 (file)
@@ -76,6 +76,7 @@
 #include "BKE_material.h"
 #include "BKE_modifier.h"
 #include "BKE_object.h"
+#include "BKE_report.h"
 #include "BKE_screen.h"
 #include "BKE_scene.h"
 #include "BKE_sequence.h"
@@ -3092,13 +3093,38 @@ void outliner_operation_menu(Scene *scene, ARegion *ar, SpaceOops *soops)
 /* These operators are only available in databrowser mode for now, as
  * they depend on having RNA paths and/or hierarchies available.
  */
-
+enum {
+       KEYINGSET_EDITMODE_ADD  = 0,
+       KEYINGSET_EDITMODE_REMOVE,
+} eKeyingSet_EditModes;
+
+/* typedef'd function-prototype style for KeyingSet operation callbacks */
+typedef void (*ksEditOp)(SpaceOops *soops, KeyingSet *ks, TreeElement *te, TreeStoreElem *tselem);
+/* Utilities ---------------------------------- */
+/* specialised poll callback for these operators to work in Datablocks view only */
+static int ed_operator_outliner_datablocks_active(bContext *C)
+{
+       ScrArea *sa= CTX_wm_area(C);
+       if ((sa) && (sa->spacetype==SPACE_OOPS)) {
+               SpaceOops *so= (SpaceOops *)CTX_wm_space_data(C);
+               return ((so->type == SO_OUTLINER) && (so->outlinevis == SO_DATABLOCKS));
+       }
+       return 0;
+}
 /* find the 'active' KeyingSet, and add if not found (if adding is allowed) */
 // TODO: should this be an API func?
 static KeyingSet *verify_active_keyingset(Scene *scene, short add)
 {
        KeyingSet *ks= NULL;
        
+       /* sanity check */
+       if (scene == NULL)
+               return NULL;
+       
        /* try to find one from scene */
        if (scene->active_keyingset > 0)
                ks= BLI_findlink(&scene->keyingsets, scene->active_keyingset-1);
@@ -3113,22 +3139,183 @@ static KeyingSet *verify_active_keyingset(Scene *scene, short add)
        return ks;
 }
 
-/* ---------------------------------- */
+/* helper func to add a new KeyingSet Path */
+static void ks_editop_add_cb(SpaceOops *soops, KeyingSet *ks, TreeElement *te, TreeStoreElem *tselem)
+{
+       ListBase hierarchy = {NULL, NULL};
+       LinkData *ld;
+       TreeElement *tem;
+       TreeStoreElem *tse;
+       PointerRNA *ptr;
+       PropertyRNA *prop;
+       ID *id = NULL;
+       char *path=NULL, *newpath=NULL;
+       int array_index= 0;
+       int flag= KSP_FLAG_GROUP_KSNAME;
+       
+       /* optimise tricks:
+        *      - Don't do anything if the selected item is a 'struct', but arrays are allowed
+        */
+       if (tselem->type == TSE_RNA_STRUCT)
+               return;
+       
+       //printf("ks_editop_add_cb() \n");
+       
+       /* Overview of Algorithm:
+        *      1. Go up the chain of parents until we find the 'root', taking note of the 
+        *         levels encountered in reverse-order (i.e. items are added to the start of the list
+        *      for more convenient looping later)
+        *      2. Walk down the chain, adding from the first ID encountered 
+        *         (which will become the 'ID' for the KeyingSet Path), and build a  
+        *              path as we step through the chain
+        */
+       // XXX do we want to separate this part out to a helper func for the other editing op at some point?
+        
+       /* step 1: flatten out hierarchy of parents into a flat chain */
+       for (tem= te->parent; tem; tem= tem->parent) {
+               ld= MEM_callocN(sizeof(LinkData), "LinkData for ks_editop_add_cb()");
+               ld->data= tem;
+               BLI_addhead(&hierarchy, ld);
+       }
+       
+       /* step 2: step down hierarchy building the path (NOTE: addhead in previous loop was needed so that we can loop like this) */
+       for (ld= hierarchy.first; ld; ld= ld->next) {
+               /* get data */
+               tem= (TreeElement *)ld->data;
+               tse= TREESTORE(tem);
+               ptr= &tem->rnaptr;
+               prop= tem->directdata;
+               
+               /* check if we're looking for first ID, or appending to path */
+               if (id) {
+                       /* just 'append' property to path 
+                        *      - to prevent memory leaks, we must write to newpath not path, then free old path + swap them
+                        */
+                       // TODO: how to do this?
+                       //newpath= RNA_path_append(path, NULL, prop, index, NULL);
+                       
+                       if (path) MEM_freeN(path);
+                       path= newpath;
+               }
+               else {
+                       /* no ID, so check if entry is RNA-struct, and if that RNA-struct is an ID datablock to extract info from */
+                       if (tse->type == TSE_RNA_STRUCT) {
+                               /* ptr->data not ptr->id.data seems to be the one we want, since ptr->data is sometimes the owner of this ID? */
+                               if (RNA_struct_is_ID(ptr))
+                                       id= (ID *)ptr->data;
+                       }
+               }
+       }
+       
+       /* step 3: if we've got an ID, add the current item to the path */
+       if (id) {
+               /* add the active property to the path */
+               // if array base, add KSP_FLAG_WHOLE_ARRAY
+               ptr= &te->rnaptr;
+               prop= te->directdata;
+               
+               /* array checks */
+               if (tselem->type == TSE_RNA_ARRAY_ELEM) {
+                       /* item is part of an array, so must set the array_index */
+                       array_index= te->index;
+               }
+               else if (RNA_property_array_length(ptr, prop)) {
+                       /* entire array was selected, so keyframe all */
+                       flag |= KSP_FLAG_WHOLE_ARRAY;
+               }
+               
+               /* path */
+               newpath= RNA_path_append(path, NULL, prop, 0, NULL);
+               if (path) MEM_freeN(path);
+               path= newpath;
+               
+               printf("Adding KeyingSet '%s': Path %s %d \n", ks->name, path, array_index);
+               
+               /* add a new path with the information obtained (only if valid) */
+               // TODO: what do we do with group name? for now, we don't supply one, and just let this use the KeyingSet name
+               if (path)
+                       BKE_keyingset_add_destination(ks, id, NULL, path, array_index, flag);
+       }
+       
+       /* free temp data */
+       if (path) MEM_freeN(path);
+       BLI_freelistN(&hierarchy);
+}
+
+/* Recursively iterate over tree, finding and working on selected items */
+static void do_outliner_keyingset_editop(SpaceOops *soops, KeyingSet *ks, ListBase *tree, ksEditOp edit_cb)
+{
+       TreeElement *te;
+       TreeStoreElem *tselem;
+       
+       for (te= tree->first; te; te=te->next) {
+               tselem= TREESTORE(te);
+               
+               /* if item is selected, perform operation */
+               if (tselem->flag & TSE_SELECTED) {
+                       if (edit_cb) edit_cb(soops, ks, te, tselem);
+               }
+               
+               /* go over sub-tree */
+               if ((tselem->flag & TSE_CLOSED)==0)
+                       do_outliner_keyingset_editop(soops, ks, &te->subtree, edit_cb);
+       }
+}
 
+/* Add Operator ---------------------------------- */
+
+static int outliner_keyingset_additems_exec(bContext *C, wmOperator *op)
+{
+       SpaceOops *soutliner= (SpaceOops*)CTX_wm_space_data(C);
+       Scene *scene= CTX_data_scene(C);
+       KeyingSet *ks= verify_active_keyingset(scene, 1);
+       
+       /* check for invalid states */
+       if (ks == NULL) {
+               BKE_report(op->reports, RPT_ERROR, "Operation requires an Active Keying Set");
+               return OPERATOR_CANCELLED;
+       }
+       if (soutliner == NULL)
+               return OPERATOR_CANCELLED;
+       
+       /* recursively go into tree, adding selected items */
+       // TODO: make the last arg a callback func instead...
+       do_outliner_keyingset_editop(soutliner, ks, &soutliner->tree, ks_editop_add_cb);
+       
+       /* send notifiers */
+       WM_event_add_notifier(C, NC_SCENE|ND_KEYINGSET, NULL);
+       
+       return OPERATOR_FINISHED;
+}
 
 void OUTLINER_OT_keyingset_add_selected(wmOperatorType *ot)
 {
+       /* identifiers */
        ot->idname= "OUTLINER_OT_keyingset_add_selected";
        ot->name= "Keyingset Add Selected";
+       
+       /* api callbacks */
+       ot->exec= outliner_keyingset_additems_exec;
+       ot->poll= ed_operator_outliner_datablocks_active;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO;
 }
 
 
-/* ---------------------------------- */
+/* Remove Operator ---------------------------------- */
 
 void OUTLINER_OT_keyingset_remove_selected(wmOperatorType *ot)
 {
+       /* identifiers */
        ot->idname= "OUTLINER_OT_keyingset_remove_selected";
        ot->name= "Keyingset Remove Selected";
+       
+       /* api callbacks */
+       ot->poll= ed_operator_outliner_datablocks_active;
+       
+       /* flags */
+       ot->flag = OPTYPE_UNDO;
 }
 
 /* ***************** DRAW *************** */