Wiggle bones (a jiggle bone implementation for 2.8)

Update Aug 14, 2019:
-bugfix for issue that caused dynamics to stop working

wiggle_bones1_4_3.py (39.5 KB)

Update July 29, 2019:
-added a new bake option that creates an additive nla strip with the baked jiggle and gives the option to disable physics on the baked bones, the entire baked armature, or the entire scene
-also exposed the ability to manually turn off jiggle bones per armature
-bone ui feedback when jiggle bones have been disabled on the armature or scene
-code that hopefully stops the addon from crashing renders (but jiggle doesn’t show up in render without baking)

-re-upload to make bake operator better
wiggle_bones1_4_2.py (39.9 KB)

Quick Fix July 12, 2019:
-wasn’t properly un-registering a class which threw errors when disabling the add-on.
wiggle_bones1_4_1.py (35.5 KB)

I was sharing some test videos of this in the blender tests forum.

https://blenderartists.org/t/jiggle-bones-in-2-8/1150269

I figured I should share it out as I think I’ve gotten it to a reasonably usable state for people to test out. the major caveat being I’m no pro at coding, and just sorta hacked together something that felt right for my needs. so please play around with it but know its probably not perfect, and if there are any smart coders out there who can point me to any obvious mistakes I’m making I’m happy to see it made better. mostly I just want those sweet jiggle bones in 2.8!

a couple tweaks that I tried to address from the 2.7x implementation that inspired this:

-you can directly keyframe wiggle bone location freely as the dynamic effect is applied to the bone rotation and y-scale (for simple rubbery stretching effects along the bone length)

-by using the ‘animated’ option, you can also keyframe a bone’s rotation and the jiggle physics will automatically be applied on top of this in an intuitive way. I can probably improve this in future iterations by determining whether there are rotation keyframes automatically and making the underlying logic for handling this case transparent to the user.

-the physics should reset when the animation loops back to the scene start frame, avoiding the bone thinking there’s been some massive force when it snaps back to this start position

-at least in my own testing, i find my implementation doesn’t seem to slow down as much with more complex rig setups. the per-frame calculations try to limit themselves as much as possible to a list of active jiggle bones.

-this is a plus for me at least: the physics are not frame-rate dependant, meaning the jiggle amount won’t change with frame rate which I find makes previewing and exporting more consistent.

todo:
-gravity (making progress on it)
-bone twist jiggle (right now it doesn’t have any momentum for rotations around the lengthwise axis)
-either simple collisions or at least rotation limits to help bouncy bits from intersecting other things

bl_info = {
    "name": "Wiggle Bone",
    "author": "Steve Miller",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "Properties > Bone",
    "description": "Simulates simple jiggle physics on bones",
    "warning": "",
    "wiki_url": "",
    "category": "Animation",
}

import bpy, math, mathutils
from mathutils import Vector,Matrix
from bpy.app.handlers import persistent

def jiggle_list_refresh():
    #iterate through all objects and bones to construct jiggle lists
    bpy.context.scene.jiggle_list.clear()
    for ob in bpy.context.scene.objects:
        if ob.type == 'ARMATURE':
            ob.jiggle_list.clear()
            for b in ob.pose.bones:
                if b.jiggle_enable:
                    item=ob.jiggle_list.add()
                    item.name = b.name
                    b['jiggle_mat']=b.matrix.copy()
                    print("added %s" %b.name)
            if ob.jiggle_list:
                item=bpy.context.scene.jiggle_list.add()
                item.name = ob.name
    #print('list refreshed')
                
def jiggle_list_refresh_ui(self,context):
    jiggle_list_refresh()
                    
#return m2 vector in m1 space
def relative_vector(m1,m2):
    mat = m2.inverted() @ m1
    vec = (mat.inverted().to_euler().to_matrix().to_4x4() @ Matrix.Translation(mat.translation)).translation
    return vec

def jiggle_bone(b):
    #translational movement between frames in bone's orientation space
    vec = relative_vector(b.matrix, Matrix(b['jiggle_mat']))  

    #rotational movement between frames
    rot1 = b.id_data.convert_space(pose_bone = b, matrix=Matrix(b['jiggle_mat']),from_space='WORLD', to_space='LOCAL').to_euler()
    if b.rotation_mode == 'QUATERNION':
        rot2 = b.rotation_quaternion.to_euler()
    else:
        rot2 = b.rotation_euler
    deltarot = Vector((rot1.z-rot2.z, 0, rot2.x-rot1.x))
                        
    b['jiggle_mat']=b.matrix.copy()
    tension = Vector(b.jiggle_spring)+vec
    if b.jiggle_animated:
        #print(deltarot)
        tension += deltarot
    b.jiggle_velocity = (Vector(b.jiggle_velocity)-tension*b.jiggle_stiffness)*(1-b.jiggle_dampen)
    b.jiggle_spring = tension+Vector(b.jiggle_velocity)
    
    #first frame should not consider any previous frame
    if bpy.context.scene.frame_current == bpy.context.scene.frame_start:
        vec = Vector((0,0,0))
        deltarot = Vector((0,0,0))
        b.jiggle_velocity = Vector((0,0,0))
        b.jiggle_spring = Vector((0,0,0))
        tension = Vector((0,0,0))
    
    additional = Vector((0,0,0))
    if b.jiggle_animated:
        if b.rotation_mode=='QUATERNION':
            additional = b.rotation_quaternion.to_euler()
        else:
            additional = b.rotation_euler
    if b.rotation_mode == 'QUATERNION':
        rotation_euler = b.rotation_quaternion.to_euler()
    else:
        rotation_euler = b.rotation_euler
    if b.rotation_mode == 'QUATERNION':
        rotation_euler.x = additional.x + math.radians(tension.z*-b.jiggle_amplitude)
        rotation_euler.z = additional.z + math.radians(tension.x*+b.jiggle_amplitude)
    else:
        rotation_euler.x = additional.x + math.radians(tension.z*-b.jiggle_amplitude)
        rotation_euler.z = additional.z + math.radians(tension.x*+b.jiggle_amplitude)
        
    #if not (bpy.context.scene.frame_current == bpy.context.scene.frame_start):
    if b.rotation_mode == 'QUATERNION':
        b.rotation_quaternion = rotation_euler.to_quaternion()
    else:
        b.rotation_euler = rotation_euler
    b.scale.y = 1-vec.y*b.jiggle_stretch

@persistent
def jiggle_bone_noanim(self):
    for item in bpy.context.scene.jiggle_list:
        if bpy.data.objects.find(item.name) >= 0:
            ob = bpy.data.objects[item.name]
            if ob.type == 'ARMATURE':
                for item2 in ob.jiggle_list:
                    if ob.pose.bones.find(item2.name) >= 0:
                        b = ob.pose.bones[item2.name]
                        if b.jiggle_enable:
                            if not b.jiggle_animated:
                                #print('jiggling %s' %b.name)
                                jiggle_bone(b)

@persistent                
def jiggle_bone_anim(self):
    for item in bpy.context.scene.jiggle_list:
        if bpy.data.objects.find(item.name) >= 0:
            ob = bpy.data.objects[item.name]
            if ob.type == 'ARMATURE':
                for item2 in ob.jiggle_list:
                    if ob.pose.bones.find(item2.name) >= 0:
                        b = ob.pose.bones[item2.name]
                        if b.jiggle_enable:
                            if b.jiggle_animated:
                                jiggle_bone(b)
                            #regardless of animation, always grab copy of matrix on late update of first frame
                            if bpy.context.scene.frame_current == bpy.context.scene.frame_start:
                                b['jiggle_mat']=b.matrix.copy()
    
class JiggleBonePanel(bpy.types.Panel):
    bl_label = 'Wiggle Bone'
    bl_idname = 'OBJECT_PT_jiggle_panel'
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = 'bone'
    
    def draw(self,context):
        layout = self.layout
        b = context.active_pose_bone
        layout.prop(b, 'jiggle_enable')
        layout.prop(b, 'jiggle_animated')
        layout.prop(b, 'jiggle_stiffness')
        layout.prop(b,'jiggle_dampen')
        layout.prop(b, 'jiggle_amplitude')
        layout.prop(b, 'jiggle_stretch')
        
class jiggle_bone_item(bpy.types.PropertyGroup):
    name: bpy.props.StringProperty()

def register():
    
    bpy.utils.register_class(jiggle_bone_item)
    bpy.utils.register_class(JiggleBonePanel)
    
    bpy.types.PoseBone.jiggle_spring = bpy.props.FloatVectorProperty(default=Vector((0,0,0)))
    bpy.types.PoseBone.jiggle_velocity = bpy.props.FloatVectorProperty(default=Vector((0,0,0)))
    bpy.types.Scene.jiggle_list = bpy.props.CollectionProperty(type=jiggle_bone_item)
    bpy.types.Object.jiggle_list = bpy.props.CollectionProperty(type=jiggle_bone_item)
    bpy.types.PoseBone.jiggle_enable = bpy.props.BoolProperty(
        name = 'Enabled:',
        description = 'activate as jiggle bone',
        default = False,
        update = jiggle_list_refresh_ui
    )
    bpy.types.PoseBone.jiggle_animated = bpy.props.BoolProperty(
        name = 'Animated:',
        description = 'enable if bone has rotational keyframes',
        default = False
    )
    bpy.types.PoseBone.jiggle_dampen = bpy.props.FloatProperty(
        name = 'Dampening:',
        description = '0-1 range of how much tension is lost per frame, higher values settle quicker',
        default = 0.2
    )
    bpy.types.PoseBone.jiggle_stiffness = bpy.props.FloatProperty(
        name = 'Stiffness:',
        description = '0-1 range of how quickly bone tries to get to neutral state, higher values give faster jiggle',
        default = 0.2
    )
    bpy.types.PoseBone.jiggle_amplitude = bpy.props.FloatProperty(
        name = 'Amplitude:',
        description = 'Multiplier for the amplitude of the spring, higher values make larger jiggles',
        default = 30
    )
    bpy.types.PoseBone.jiggle_stretch = bpy.props.FloatProperty(
        name = 'Stretching:',
        description = '0-1 range for how much the jiggle stretches the bone, higher values stretch more',
        default = .4
    )
    
    #bpy.app.handlers.frame_change_pre.clear()
    #bpy.app.handlers.frame_change_post.clear()
    bpy.app.handlers.frame_change_pre.append(jiggle_bone_noanim)
    bpy.app.handlers.frame_change_post.append(jiggle_bone_anim)

def unregister():
    bpy.utils.unregister_class(JiggleBonePanel)
    bpy.utils.unregister_class(jiggle_bone_item)
    
    bpy.app.handlers.frame_change_pre.remove(jiggle_bone_noanim)
    bpy.app.handlers.frame_change_post.remove(jiggle_bone_anim)

if __name__ == "__main__":
    register()

#TODO
#jiggle props into property group < seems to not work for defaut vals/descriptions/etc and : vs = assignment

#run as addon [DONE]
#rotational momentum on animated bones [kinda done]

#handle quaternion rotation? [DONE]

#gravity?
#simple collision?
#reset physics on start frame [DONE]
20 Likes

amazing … it works very well, really like it and looking forward for more features :slight_smile: thank you so much

also for future development , if you need any help in testing then let me know,

I´m interested in this. Do you have a complete walkthrough setup and workflow video?

I’ll try to make a demo video at some point, but for now:

1.1 UPDATE!

handling of animated vs non animated bones is now handled transparently, hopefully making things more straightforward

non animated bones define their rest pose from their orientation when the wiggle bone is enabled. you can update this rest pose by re-toggling the enabled state.

you can change multiple jiggle bone properties at once (shamelessly borrowed from the latest demo of the 2.79 script)

bones now jiggle in response to y-axis rotations (essentially twist-jiggling)

the simple stretching along the bone y-axis was kinda broken before, its working better now

object level transforms weren’t actually being handled properly before. if you moved an armature in object mode there wouldn’t be a jiggle response, and worse if the armature was rotated, it would break. both cases should be working now.

wiggle_bones.py (10.5 KB)

edit:
for some reason, the multi-bone editing crashes with more than 2 bones selected. seeing if I can figure out why, but in the meantime, editing more than 2 at once is crash city! sorry!

edit 2:
got it sorted and updated the link in this post. infinite credit to Simón Flores’ solution in his jiggle plugin (which is ported to 2.8 itself now!!). I’m still playing around with mine because i think its a more simplified version of the jiggle effect that seems to run comparatively fast.

4 Likes

Hey shteeve, i would like to give this a test, but alas i have no idea how to get this to work.
I created a zip with a folder named wiggle_bones and your *.py in there, but it wont show up
in the addons list when i install it from said zip.
Could you explain the process to me please?

To my experience no need for a zip file, just select the py file directly when choosing to install an add on in blender preferences. See if that works?

1 Like

Thank you shteeve, that did it.
However, its only working in the viewport and not when rendered in an animation via eevee.
Any idea about that?

1 Like

hmm, the quirks of scene updates is definitely one of the things I’m still working to sort out, as I think its also cropping up in various other edge case issues.

for now, some methods around this are to either use the viewport render option (where you can set it to rendered and turn off overlays) or to use “bake action” to bake the wiggle bone animation to traditional keyframes.

1 Like

Ok baking worked, but the result is different to the viewport:

I do understand that this is still beta and maybe some stuff to be fixed from a side you cannot access.
Thank you again though, your efforts are appreciated!

thats odd that its giving different results after baking, which generally works for me.

are you disabling the wiggle bone effect after the bake? (as it might be doubling up on the effect)

I’ve actually recently tweaked the script to add a global scene checkbox that lets you mute the dynamic effect of all wiggle bones. I’m using the baking technique myself for exporting fbx’s to unity and found that helpful. I’m thinking it’d probably be worth adding object level switches too, to give users maximum flexibility over what is muted.

[update] pulled version here, its being glitchy and I’ll look in more tonight after work.

update again, just had the wrong file!

wiggle_bones1_2_1.py (11.2 KB)

1 Like

I dont want to start another post with saying thank you, but you know how this will end.
So, as always with blender it turns out its my fault when an error occurs.
Your addon works wonderful with the right parameters and when baked with visual keying.
I now have what i wanted, breast jiggle, so once again, thank you very much indeed, shteeve! :slight_smile:

no problem, glad it worked for your needs! it is still very much a work in progress but hopefully it’ll incrementally improve as I’m using it for some production work and discovering gotchas/helpful features as I go.

I have a feeling the rendering issue is tied to the fact that I apply the jiggle using a frame_change_post handler instead of frame_change_pre. the trade off is that while the “post” version allows the jiggle to be applied on top animated keyframes on the bone, it might also be happening after things like f12 renders are evaluated, hence the need to bake. I’ll keep exploring!

1 Like

I will try and get you as much feedback as i can once you release new versions.
Alas thats the only thing i can offer since i dont code. For now im pretty happy with the results though.
Have a nice weekend!

thats super cool man , now once you add gravity it will be something like this software ,


and really looking forward to it , it can be the next big thing
2 Likes

I actually am on the closed beta for Cascadeur but I haven’t had a time to boot into my windows partition to try it out, yet. It looks amazing and I’m guessing its a fair bit more sophisticated than what I’m making, but I have been using my tool to add some add some nice secondary motion to a project at work.

getting gravity working right has involved sorting out a bunch of stuff that I didn’t really consider when first making my script, but I’m making progress!

here’s a little sneak peak:

7 Likes

that gravity looks super cool , i can test it on actual character , let me know if that helps :slight_smile:

update!

-gravity! (adjustable per bone to get the effect you want)

-jiggle momentum transfer. previously each bone was just wiggling based on the input animation and nothing else. now if bones are in a chain, the momentum of the wiggle will transfer down the chain more believably

as always, there’s probably some edge cases I didn’t think of, so let me know how its working for you!

wiggle_bones1_3.py (13.6 KB)

3 Likes

Hello shteeve. Thank you for great addon. It made super easy to set up physics for rigs and bone chains. Will you update addon for 2.79? I am using BGE and your addon is very handy, but in 2.8 we have no game engine or interactive mode yet. Would be nice if you will update addon with new features for 2.79. Thank you.

If you need more refs to make more useful for blender I have some videos explaining how it works in maya.