[Bf-blender-cvs] [d219b09a480] asset-browser-poselib: Action: flip action data using pose contents

Campbell Barton noreply at git.blender.org
Thu Mar 25 18:02:16 CET 2021


Commit: d219b09a4802fff37189442de55720da6a03ea78
Author: Campbell Barton
Date:   Thu Mar 25 12:45:48 2021 +0100
Branches: asset-browser-poselib
https://developer.blender.org/rBd219b09a4802fff37189442de55720da6a03ea78

Action: flip action data using pose contents

This adds a new RNA method `Action.flip_with_pose(ob, frame)`
to flip the action channels that control a pose.
The rest-pose it's self is used to properly flip the bones transformation.

This is useful as a way to flip actions used in pose libraries,
so the same action need not be included for each side.

API calls to cache F-curve look-ups have also been added,
supporting a single hash lookup to access all channels controlling an RNA path.

----

**Motivation**

This patch adds functionality to flip an entire action on the X axis using a symmetrical rig.

This was written so the asset manager can apply poses flipped, see T86159,

tested with spring.blend.

**Alternative Solutions**

It's possible to calculate this on the pose directly (see D10766), however operating on the action data makes this more useful as an API function, since the data can be flipped before it's applied to the pose.
Instead of flipping on the pose, then writing back to the action.

**Limitations**

There is some information not easily available at the time of flipping, for example - it's possible the flipped data-path doesn't exist in all cases, although the ideal behavior isn't obvious when the RNA path only resolves on one side of the pose.

- Currently the API function flips all channels, we could add an argument so it only operates on some of the channels.

**Open Topics**

- When some actions transform channels don't exist, they could be created (for example - if only X & Y rotation are set, it may be necessary to create a Z F-Curve to properly flip the rotation).
- The key-framing API is currently part of the editors (it would be a bad-level call from the BKE), we could expose some keyframing functionality to BKE, or move this functionality to editors. For now there is a simple keyframe adjusting function.
- Currently only the armature rest-matrices is used, not the pose. This API could take an armature instead of an object, although we might want to use pose content in the future, it doesn't seem like a big issue either way.
- This could eventually be exposed as an operator.
- Currently this re-orders channels which could have other frames keyed. A more comprehensive approach could be to operate on all keyed frames for an action, so an action that includes multiple poses will have them all properly flipped.

-----

**Notes**

- The F-Curve lookup cache can be committed separately.

- This is an example script for testing with the pose object set active:

```
import bpy
from bpy import context
ob = context.object
action = bpy.data.actions['07_040_A.spring']
action.flip_with_pose(ob, context.scene.frame_current)
```

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

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

M	source/blender/blenkernel/BKE_action.h
M	source/blender/blenkernel/BKE_fcurve.h
M	source/blender/blenkernel/intern/action.c
M	source/blender/blenkernel/intern/fcurve.c
M	source/blender/makesrna/intern/rna_action_api.c

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

diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h
index 717cfa607ad..63709fb2da1 100644
--- a/source/blender/blenkernel/BKE_action.h
+++ b/source/blender/blenkernel/BKE_action.h
@@ -219,6 +219,8 @@ bool BKE_pose_copy_result(struct bPose *to, struct bPose *from);
 /* Clear transforms. */
 void BKE_pose_rest(struct bPose *pose, bool selected_bones_only);
 
+void BKE_action_flip_with_pose(struct bAction *act, struct Object *ob_arm);
+
 /* Tag pose for recalc. Also tag all related data to be recalc. */
 void BKE_pose_tag_recalc(struct Main *bmain, struct bPose *pose);
 
diff --git a/source/blender/blenkernel/BKE_fcurve.h b/source/blender/blenkernel/BKE_fcurve.h
index 4569e68ea4a..dff01931d59 100644
--- a/source/blender/blenkernel/BKE_fcurve.h
+++ b/source/blender/blenkernel/BKE_fcurve.h
@@ -193,6 +193,18 @@ void BKE_fcurve_foreach_id(struct FCurve *fcu, struct LibraryForeachIDData *data
 /* find matching F-Curve in the given list of F-Curves */
 struct FCurve *BKE_fcurve_find(ListBase *list, const char rna_path[], const int array_index);
 
+/* Cached f-curve look-ups, use when this needs to be done many times. */
+struct FCurvePathCache;
+struct FCurvePathCache *BKE_fcurve_pathcache_create(ListBase *list);
+void BKE_fcurve_pathcache_destroy(struct FCurvePathCache *fcache);
+struct FCurve *BKE_fcurve_pathcache_find(struct FCurvePathCache *fcache,
+                                         const char rna_path[],
+                                         const int array_index);
+int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache,
+                                    const char *rna_path,
+                                    struct FCurve **fcurve_result,
+                                    int fcurve_result_len);
+
 struct FCurve *BKE_fcurve_iter_step(struct FCurve *fcu_iter, const char rna_path[]);
 
 /* high level function to get an fcurve from C without having the rna */
@@ -245,6 +257,14 @@ bool BKE_fcurve_calc_bounds(struct FCurve *fcu,
                             const bool do_sel_only,
                             const bool include_handles);
 
+float *BKE_fcurves_calc_keyed_frames_ex(struct FCurve **fcurve_array,
+                                        int fcurve_array_len,
+                                        const float interval,
+                                        int *r_frames_len);
+float *BKE_fcurves_calc_keyed_frames(struct FCurve **fcurve_array,
+                                     int fcurve_array_len,
+                                     int *r_frames_len);
+
 void BKE_fcurve_active_keyframe_set(struct FCurve *fcu, const struct BezTriple *active_bezt);
 int BKE_fcurve_active_keyframe_index(const struct FCurve *fcu);
 
diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c
index f9c2a4e53ad..4dc9a5d9224 100644
--- a/source/blender/blenkernel/intern/action.c
+++ b/source/blender/blenkernel/intern/action.c
@@ -2033,3 +2033,417 @@ void BKE_pose_blend_read_expand(BlendExpander *expander, bPose *pose)
     BLO_expand(expander, chan->custom);
   }
 }
+
+/* -------------------------------------------------------------------- */
+/** \name Flip the Action (Armature/Pose Objects)
+ *
+ * This flips the action using the rest pose (not the evaluated pose).
+ *
+ * Details:
+ *
+ * - Key-frames are modified in-place, creating new key-frames is not yet supported.
+ *   That could be useful if a user for example only has 2x rotation channels set.
+ *   In practice users typically keyframe all rotation channels or none.
+ *
+ * - F-curve modifiers are disabled for evaluation,
+ *   so the values written back to the keyframes don't include modifier offsets.
+ *
+ * - Sub-frame key-frames aren't supported,
+ *   this could be added if needed without much trouble.
+ *
+ * - F-curves must have a #FCurve.bezt array (sampled curves aren't supported).
+ * \{ */
+
+/**
+ * This structure is created for each pose channels F-curve,
+ * an action be evaluated and stored in `fcurve_eval`,
+ * with the mirrored values written into `bezt_array`.
+ *
+ * Store F-curve evaluated values, constructed with a sorted array of rounded keyed-frames,
+ * passed to #action_flip_pchan_cache_init.
+ */
+struct FCurve_KeyCache {
+  FCurve *fcurve;
+  /**
+   * Cached evaluated F-curve values (without modifiers).
+   */
+  float *fcurve_eval;
+  /**
+   * Cached #FCurve.bezt values, NULL when no key-frame exists on this frame.
+   *
+   * \note The case where two keyframes round to the same frame isn't supported.
+   * In this case only the first will be used.
+   */
+  BezTriple **bezt_array;
+};
+
+/**
+ * Assign `fkc` path, using a `path` lookup for a single value.
+ */
+static void action_flip_pchan_cache_fcurve_assign_value(struct FCurve_KeyCache *fkc,
+                                                        int index,
+                                                        const char *path,
+                                                        struct FCurvePathCache *fcache)
+{
+  FCurve *fcu = BKE_fcurve_pathcache_find(fcache, path, index);
+  if (fcu && fcu->bezt) {
+    fkc->fcurve = fcu;
+  }
+}
+
+/**
+ * Assign #FCurve_KeyCache.fcurve path, using a `path` lookup for an array.
+ */
+static void action_flip_pchan_cache_fcurve_assign_array(struct FCurve_KeyCache *fkc,
+                                                        int fkc_len,
+                                                        const char *path,
+                                                        struct FCurvePathCache *fcache)
+{
+  FCurve **fcurves = alloca(sizeof(*fcurves) * fkc_len);
+  if (BKE_fcurve_pathcache_find_array(fcache, path, fcurves, fkc_len)) {
+    for (int i = 0; i < fkc_len; i++) {
+      if (fcurves[i] && fcurves[i]->bezt) {
+        fkc[i].fcurve = fcurves[i];
+      }
+    }
+  }
+}
+
+/**
+ * Fill in pose channel cache for each frame in `keyed_frames`.
+ *
+ * \param keyed_frames: An array of keyed_frames to evaluate,
+ * note that each frame is rounded to the nearest int.
+ * \param keyed_frames_len: The length of the `keyed_frames` array.
+ */
+static void action_flip_pchan_cache_init(struct FCurve_KeyCache *fkc,
+                                         const float *keyed_frames,
+                                         int keyed_frames_len)
+{
+  BLI_assert(fkc->fcurve != NULL);
+
+  /* Cache the F-curve values for `keyed_frames`. */
+  const int fcurve_flag = fkc->fcurve->flag;
+  fkc->fcurve->flag |= FCURVE_MOD_OFF;
+  fkc->fcurve_eval = MEM_mallocN(sizeof(float) * keyed_frames_len, __func__);
+  for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) {
+    const float evaltime = keyed_frames[frame_index];
+    fkc->fcurve_eval[frame_index] = evaluate_fcurve_only_curve(fkc->fcurve, evaltime);
+  }
+  fkc->fcurve->flag = fcurve_flag;
+
+  /* Cache the #BezTriple for `keyed_frames`, or leave as NULL. */
+  fkc->bezt_array = MEM_mallocN(sizeof(*fkc->bezt_array) * keyed_frames_len, __func__);
+  BezTriple *bezt = fkc->fcurve->bezt;
+  BezTriple *bezt_end = fkc->fcurve->bezt + fkc->fcurve->totvert;
+
+  int frame_index = 0;
+  while (frame_index < keyed_frames_len) {
+    const float evaltime = keyed_frames[frame_index];
+    const float bezt_time = roundf(bezt->vec[1][0]);
+    if (bezt_time > evaltime) {
+      fkc->bezt_array[frame_index++] = NULL;
+    }
+    else {
+      if (bezt_time == evaltime) {
+        fkc->bezt_array[frame_index++] = bezt;
+      }
+      bezt++;
+      if (bezt == bezt_end) {
+        break;
+      }
+    }
+  }
+  /* Clear remaining unset keyed_frames (if-any). */
+  while (frame_index < keyed_frames_len) {
+    fkc->bezt_array[frame_index++] = NULL;
+  }
+}
+
+/**
+ */
+static void action_flip_pchan(Object *ob_arm,
+                              const bPoseChannel *pchan,
+                              struct FCurvePathCache *fcache)
+{
+  /* Begin F-Curve pose channel value extraction. */
+  /* Use a fixed buffer size as it's known this can only be at most:
+   * `pose.bones["{MAXBONENAME}"].rotation_quaternion`. */
+  char path_xform[256];
+  char pchan_name_esc[sizeof(((bActionChannel *)NULL)->name) * 2];
+  BLI_str_escape(pchan_name_esc, pchan->name, sizeof(pchan_name_esc));
+  int path_xform_prefix_len = SNPRINTF(path_xform, "pose.bones[\"%s\"]", pchan_name_esc);
+  char *path_xform_suffix = path_xform + path_xform_prefix_len;
+  int path_xform_suffix_len = sizeof(path_xform) - path_xform_prefix_len;
+
+  /* Lookup and assign all available #FCurve channels,
+   * unavailable channels are left NULL. */
+
+  /**
+   * Structure to store transformation F-curves corresponding to a pose bones transformation.
+   * Match struct member names from #bPoseChannel so macros avoid repetition.
+   *
+   * \note There is no need to read values unless they influence the 4x4 transform matrix,
+   * and no need to write values back unless they would be changed by a modified matrix.
+   * So `rotmode` needs to be read, but doesn't need to be written back to.
+   *
+   * Most bendy-bone settings don't need to be included either, flipping their RNA paths is enough.
+   * Although the X/Y settings could make sense to transform, in practice it would only
+   * work well if the rotation happened to swap X/Y alignment, leave this for now.
+   */
+  struct {
+    struct FCurve_KeyCache loc[3], eul[3], quat[4], rotAxis[3], rotAngle, size[3], rotmode;
+  } fkc_pchan = {{{NULL}}};
+
+#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index) \
+  BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \
+  action_flip_pchan_cache_fcurve_assign_value(&fkc_pchan.id, index, path_xform, fcache)
+
+#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix) \
+  BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \
+  action_flip_pchan_cache_fcurve_assign_array( \
+      fkc_pchan.id, ARRAY_SIZE(fkc_pchan.id), path_xform, fcache)
+
+  FCURVE_ASSIGN_ARRAY(loc, ".location");
+  FCURVE_ASSIGN_ARRAY(eul, ".rotation_euler");
+  FCURVE_ASSIGN_ARRAY(quat, ".rotation_quaternion");
+  FCURVE_ASSIGN_ARRAY(rotAxis, ".rotation_axis_angle");
+  FCURVE_ASSIGN_VALUE(rotAngle, ".rotation_axis_angle", 3);
+  FCURVE_ASSIGN_ARR

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list