5
$\begingroup$

I'm trying to modify Blender to add something I call (informally) "bone inertia". It's a bit like having a soft body attached to a parent bone that's used to drive the child bone. The difference is that I really want to use my own "physics" instead of going through Blender's Soft Body physics.

So far, I've been trying to achieve this using Python. I've got a script that does the following:

  1. Adds a few properties to bpy.types.PoseBone. I call them "Strength", "Inertia" and "Limit". I animate those properties so that I can turn the behavior on, off or tweak it in the middle of an animation.
  2. Adds a panel to the bone property tab that exposes those properties (pose mode only).
  3. Registers a callback to bpy.app.handlers.frame_change_post.

In the callback, I check to see if an animation is playing (bpy.context.window.screen.is_animation_playing). If it is, I scan all objects in the scene and apply my own "bone inertia physics" to every armature found. For each bone, I keep track of some state from the previous frame, eg. the velocity of the bone's tail and some other things. Ultimately, I overwrite each bone's location/orientation by setting the PoseBone's matrix attribute. If no animation is playing, I don't modify the bones at all. That way, the user can pose the armature as they normally would, but as soon as they hit Play, they see the "bone inertia" applied to what they've done. This workflow is convenient enough for me.

I finally got the bones behaving exactly the way I want, but there's one problem: the mesh doesn't follow the bone locations set in the frame_change_post callback. I realized that it's because the callback is invoked after the mesh's Armature modifier is already applied.

I'm a C++ programmer so I went ahead and starting hacking on the Blender source code. I added a new Python callback, "armature_update_post", that gets called at the end of BKE_pose_where_is (right after the armature gets updated). This didn't work very well, probably because BKE_pose_where_is is called in a background thread, causing timing issues. (For example, in the Python callback, bpy.context.window was randomly set to None. I didn't investigate it too deeply.)

Now I'm thinking of re-implementing my "bone inertia" physics in C. To do it, I'll have to add new properties to bPoseChannel, including the state I need to preserve between frames (velocity, etc.), and add some code to BKE_pose_where_is_bone. I'm not sure what issues I'll encounter using this approach yet. For example, I'm not sure if I can reliably determine whether the animation is playing or not at this point, because it will still be called in a background thread.

Can anyone give me some feedback on what I'm trying to do? Is there a better way to achieve what I want?

Thanks!

$\endgroup$
1
$\begingroup$

I got it working. Leaving the answer here in case somebody else is looking for a way to develop a similar kind of custom "armature animation modifier" (if I can call it that). Again if anyone sees a better way, let me know!

Basically, I dug a little deeper into why the armature_update_post Python callback (which I added to BKE_pose_where_is, as explained above) wasn't working reliably. The problem was that BKE_pose_where_is can be invoked from an arbitrary thread, and therefore the Python callback runs in an arbitrary thread. In this Python callback, I was checking bpy.context.window.screen.is_animation_playing but this doesn't work because bpy.context.window returns None if not called from the main thread. (See ctx_wm_python_context_get in context.c.) So instead, I decided to add yet another Python callback so that the UI can tell the script when animation playback starts & stops, and let the script keep track of the playback state itself.

Here's a summary of the approach I'm using now (based on Blender 2.78):

  • Add a new Python callback armature_update_post and invoke it from BKE_pose_where_is in armature.c.
  • Add a new Python callback animation_toggle and invoke it from ED_screen_animation_play in screen_ops.c.
  • In rna_pose.c, add a call to BKE_pose_where_is_bone at the end of rna_PoseChannel_matrix_set, so that when a PoseBone's matrix attribute is set from Python, the bone is fully updated right away (specifically the chan_mat member of bPoseChannel). This was necessary because rna_PoseChannel_matrix_set seems to leave the bPoseChannel in a partially updated state. If you don't do this, the whole approach doesn't work.
  • Handle the callbacks in the Python script. In armature_update_post, if animation is playing, modify the PoseBone matrices however you want, in parent-to-child order (basically just scan obj.pose.bones sequentially).

That's just the general approach at a high level. As you can see, I wasn't able to implement a pure Python solution -- I had to hack in C a little bit. The Python callback runs kind of slowly, so the playback rate in Blender slows down. I might move the logic into C later to speed it up, but for now, at least it's working.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.