5
$\begingroup$

I'm making some keyframes on a rig that has tweak bones. Most of my poses do not use the tweak bones, so I want them at rest pose. Occasionally a pose does need a tweak bone. This means that in every pose that uses the tweak bone, I need them keyframed at their needed position, and every pose that doesn't use a tweak bone, I have to keyframe them at Rest. And every time I scrub through an area where the tweak bones have a pose, they will stay in that pose even though they are hidden and accidentally get left on.

The result of all this is a lot of time spent hiding/showing tweak bone layers, and having to interact with them constantly, which is very inefficient.

I am looking for a way to keyframe them without having to show their layer and select them all. It seems my workflow would be much more efficient if it was possible to establish a group of bones, and be able to keyframe them all at once regardless of if they are showing or selected. It seems that the Whole Character option on the keyframe menu does this, but it also keyframes a ton of extra things that I don't want to keyframe.

Does anyone know a way to do this, or another solution to this problem?

$\endgroup$
  • $\begingroup$ What about this? $\endgroup$ – Crantisz Jan 27 '18 at 22:40
  • 1
    $\begingroup$ @Crantisz can't tell what/where that is I'm afraid. $\endgroup$ – Drudge Jan 28 '18 at 6:09
  • $\begingroup$ It's a button, that show keyframes/channels that currently invisible. $\endgroup$ – Crantisz Jan 28 '18 at 7:36
  • $\begingroup$ @Crantisz that looks like it only effects display in the Dope Sheet. Doesn't seem to have anything to do with adding keyframes to bones? $\endgroup$ – Drudge Jan 31 '18 at 1:04
  • 1
    $\begingroup$ The code to the "whole character" keyingset is available at 2.79/scripts/startup/keyingsets_builtins.py It can be pretty much copied to make a new custom keying set for your "TWEAK" bones by only iterating over bones in hidden layers, and / or by name prefix etc. A small sample file with info on which bones and what paths to keyframe would clarify somewhat. $\endgroup$ – batFINGER Jan 31 '18 at 11:07
2
+50
$\begingroup$

"Tweaking" BUILTIN_KSI_WholeCharacter class.

The class of the "whole character" keyingset, BUILTIN_KSI_WholeCharacter, is available at 2.79/scripts/startup/keyingsets_builtins.py It can be pretty much copied to make a new custom keying set for your "TWEAK" bones by only iterating over bones in hidden layers, and / or by name prefix etc. A small sample file with info on which bones and what paths to keyframe would clarify somewhat.

Test script below, adds keyframes to bones hidden either via bone.hide or via unshown layer. Prints "generate <bone.name>" for each keyframed bone.

This example edits the class method KeyingSetInfo.iterator to only iterate over hidden bones. The iterator method chooses what iterable, in this case pose bones, should have keyframes generated.

import bpy
from keyingsets_builtins import KeyingSetInfo, BUILTIN_KSI_WholeCharacter

class CUSTOM_KSI_HiddenBones(KeyingSetInfo):
    """Insert a keyframe for all properties that are likely to get animated in a character rig """
    """(only selected bones)"""
    bl_idname = "HiddenBones"
    bl_label = "Whole Character (Hidden bones only)"

    # iterator - all bones regardless of selection
    def iterator(ksi, context, ks):
        # Use either the selected bones, or all of them if none are selected.
        bones = context.selected_pose_bones or context.active_object.pose.bones
        ob = context.active_object
        arm = ob.data
        def hidden(pb):
            bone = pb.bone            
            # bone on these layers
            layers = [i for i, l in enumerate(bone.layers) if l]
            return bone.hide or not any(arm.layers[i] for i in layers)
        bones = [pb for pb in ob.pose.bones if hidden(pb)]
        for bone in bones:
            if bone.name.startswith(BUILTIN_KSI_WholeCharacter.badBonePrefixes):
                continue
            print("generate", bone.name)
            ksi.generate(context, ks, bone)

    # Poor man's subclassing. Blender breaks when we actually subclass BUILTIN_KSI_WholeCharacter.
    poll = BUILTIN_KSI_WholeCharacter.poll
    generate = BUILTIN_KSI_WholeCharacter.generate
    addProp = BUILTIN_KSI_WholeCharacter.addProp
    doLoc = BUILTIN_KSI_WholeCharacter.doLoc
    doRot4d = BUILTIN_KSI_WholeCharacter.doRot4d
    doRot3d = BUILTIN_KSI_WholeCharacter.doRot3d
    doScale = BUILTIN_KSI_WholeCharacter.doScale
    doBBone = BUILTIN_KSI_WholeCharacter.doBBone
    doCustomProps = BUILTIN_KSI_WholeCharacter.doCustomProps

classes = [CUSTOM_KSI_HiddenBones]

def register():
    from bpy.utils import register_class
    for cls in classes:
        register_class(cls)


def unregister():
    from bpy.utils import unregister_class
    for cls in classes:
        unregister_class(cls)


if __name__ == "__main__":
    register()

To make an iterator that selects bones in active bone group

bones = [pb for pb in ob.pose.bones 
        if pb.bone_group == ob.pose.bone_groups.active]

Each pose bone has its keyframes generated using the KeyingSetInfo.generate method. The generator keys in the desired keyframes for each iterable. In example above that has been "poor man's subclassed" generate = BUILTIN_KSI_WholeCharacter.generate from the whole character built in.

Let's have a look at the BUILTIN_KSI_WholeCharacter.generate method

    def generate(ksi, context, ks, bone):
        # loc, rot, scale - only include unlocked ones
        if not bone.bone.use_connect:
            ksi.doLoc(ks, bone)

        if bone.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}:
            ksi.doRot4d(ks, bone)
        else:
            ksi.doRot3d(ks, bone)
        ksi.doScale(ks, bone)

        # bbone properties?
        ksi.doBBone(context, ks, bone)

        # custom props?
        ksi.doCustomProps(ks, bone)

    # ----------------

Pretty straight forward. To remove custom properties from the keying set, could define (and hence override) the generate method, and remove or comment out ksi.doCustomProps(ks, bone) to have no custom properties keyed in.

Alternatively, could reuse the generate method and set up a dummy method and define for each of the keyframes you wish to bypass, for example do nothing for b-bones or custom properties.

def dummy(*args, **kwargs):
    return None

class CUSTOM_KSI_HiddenBones(KeyingSetInfo):
    ...
    generate = BUILTIN_KSI_WholeCharacter.generate
    doBBone = dummy
    doCustomProps = dummy
    ...
$\endgroup$
  • $\begingroup$ This seems like most of what I need. What would need to change to make it 1) keyframe all bones in a bone group, hidden or not or 2) just keyframe all bones, hidden or not? $\endgroup$ – Drudge Feb 4 '18 at 1:01
  • $\begingroup$ Also, I notice that when I use Whole Character in any capacity, it keyframes all my rigify properties in the N panel, like IK and FK switch. I'd like it to not do that. Any ideas? $\endgroup$ – Drudge Feb 4 '18 at 1:04
  • $\begingroup$ @Drudge have edited answer to cover comments above. $\endgroup$ – batFINGER Feb 6 '18 at 7:41
1
$\begingroup$

I guess this would require sripting.
Each bone can be added a property.

Then select based upon property name or property value.
Here is a question and answer in that direction :
How to check if an object has a custom property with python?

For Python commands about bones (hiding unhiding):
https://docs.blender.org/api/blender_python_api_2_59_0/bpy.ops.pose.html
BTW those python commands seam to indicate can they support groups, but using a property might be more flexible, depending on what you want.

$\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.