Making custom bone shape easily

Hello there,

I’m new to blender (learning it for one week so far) and as I was following a tutorial about making a rig, I’ve been slightly bothered when it was about making custom shapes for the bones. You know, when you set up an object as a custom shape for a bone and then it is not rotated or scaled the good way. The trick of the tutorial for this problem was to edit the object and transform it manually so that it would match the bone shape. Good trick, but it was a bit tedious (at least for me).

So I scripted it and here’s the result below. There’s not panel or button, you have to run it via space key and search for “Fit Bone Shape”.

The script opens a pop-up window and asks for three parameters:

  1. the object’s name that is set as the bone shape. (The name is pre-filled if the object has been selected previously.)
  2. the armature’s name. (Pre-filled if there’s only one armature.)
  3. the bone name. Here you have to type it.

The script translates/rotates/scales the object so that it matches the bone in the armature. Then, you can shape the object where it will be in the armature… always more intuitive isn’t it?

I’d be happy to have some feedback from more experienced users… has it been already done? is it useful to anyone? how could it be improved? etc.

Here’s the code :


'''
FitBoneShapeOperator by jilipagia, 2012-08-06.
Contact: jili.pagia at gmail.com
'''

import bpy
from bpy.props import StringProperty
from mathutils import *

class FitBoneShapeOperator(bpy.types.Operator):

    bl_idname = 'obj.fit_bone_shape'
    bl_label = 'Fit Bone Shape'
    
    objectName = StringProperty(
        name = 'Object name',
        description = 'Object to fit to the bone shape')
    
    armatureName = StringProperty(
        name = 'Armature name',
        description = 'Armature in which the bone is')
    
    boneName = StringProperty(
        name = 'Bone name',
        description = 'Target bone')
        
    def execute(self, context):

        # Get objects to work with
        shapeObject = bpy.data.objects[self.objectName]
        armature = bpy.data.objects[self.armatureName]
        bone = armature.data.bones[self.boneName]
                
        # Rotate shape object to match bone local rotation in the armature.
        shapeObject.rotation_euler = (0.0, 0.0, 0.0)
        boneChain = bone.parent_recursive
        boneChain.insert(0, bone)
        for bi in boneChain:
            rotMat = Matrix((bi.x_axis, bi.y_axis, bi.z_axis)).transposed()
            shapeObject.rotation_euler.rotate(rotMat)
        
        # Same with translation and scaling.
        shapeObject.location = bone.head_local       
        shapeObject.scale = bone.length * Vector((1.0, 1.0, 1.0))
        
        # Move object to armature coordinates system (except scaling, see below).
        rotMat = armature.rotation_euler
        shapeObject.rotation_euler.rotate(rotMat)
        shapeObject.location.rotate(rotMat)       
        shapeObject.location += armature.location
        
        # Display warning message if the armature has scaling different to one.
        s = armature.scale
        if (s.x != 1) or (s.y != 1) or (s.z != 1):
            self.report({'Warning'}, "Armature should have a scale factor = 1 to match bone shape properly")
            
        return {'FINISHED'}
        
    def invoke(self, context, event):
    
        # If one object is selected, take it as default.
        if len(context.selected_objects) == 1:
            self.objectName = bpy.context.selected_objects[0].name
            
        # If there's only one armature, take it as default.
        if len(bpy.data.armatures) == 1:
            self.armatureName = bpy.data.armatures[0].name
        
        # Open pop-up window for parameter setting.
        return context.window_manager.invoke_props_dialog(self)

if __name__ == '__main__':
    bpy.utils.register_class(FitBoneShapeOperator)

Welcome to the forums, scripts are always appreciated.

But there’s already something in the extensions you should look at that does this. It worked well the last time I used it, although it would show an error something along the lines of ‘expected a string…and found something else’ other than that it was very good.
http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Rigging/Auto_custom_shape

ebb

@jilipagia:
Just tried this and it works. Bones for the mouth rig I built were at such odd angles, I couldn’t get the shape meshes to line up. After hours of banging on it without success, I remembered reading this thread when it was first posted.

With no details on how to get the script into Blender, I leaped in and imported it into a Text Editor window, ran it, then looked for it using Spacebar Search.

After a few false starts (I kept switching the mesh and bone names in their respective boxes) I hit the button and presto-chango!

A couple of things that would be nice as additions to this time-saving tool:

  • bring it in as an addon so it’s always available if that’s what the user wants
  • have it read the selected bone name into the corresponding box
  • instruct the user to have the custom shape already set for the bone so your script can get the mesh name automatically
  • put a button in the bone panel (optional; it’s just as easy to use the Spacebar, but for completeness sake, it would be nice)

Also, I took a quick look at the features of Auto Custom Shape (mentioned by ebb) and saw no mention of this alignment thing you do.

Excellent job, sir! You’ve saved me hours!

EDIT: Correction… Auto Custom Shape does do the same thing, but the custom shape mesh is created automatically and it’s just a mesh version of a standard bone shape. From there, you edit the shape to what you want.

Since yours allows me to create the custom bone shape in advance, I found it more to my liking. I have already built a set of meshes, so having to mess with rebuilding them isn’t something I want to do.

Bottom line: I still like yours better.

I hope you don’t mind; I made a small change to your script. When you run it, the dialog now appears fully populated as long as the following criteria are met (no more typing!):

  • you’re in Pose Mode
  • a single bone is selected
  • there is a custom shape set for that bone

Make sure to have ‘wireframe’ checked in the selected bone’s Display property section/area/whatever-it’s-called.

After running the script, select the mesh, go into Edit Mode, select all vertices and move to their final position.

Here’s the changed script (again, I hope you don’t mind):



''''
FitBoneShapeOperator by jilipagia, 2012-08-06.
Modifications by rontarrant 2012-08-29
- Automatically populated all boxes in dialog
- useage is now:
   switch to Pose Mode
    select a bone
    set its Custom Shape
    call this script
Contact: jili.pagia at gmail.com
'''

import bpy
from bpy.props import StringProperty
from mathutils import *

class FitBoneShapeOperator(bpy.types.Operator):

    bl_idname = 'obj.fit_bone_shape'
    bl_label = 'Fit Bone Shape'

    objectName = StringProperty(
        name = 'Object name',
        description = 'Object to fit to the bone shape')

    armatureName = StringProperty(
        name = 'Armature name',
        description = 'Armature in which the bone is')

    boneName = StringProperty(
        name = 'Bone name',
        description = 'Target bone')

    def execute(self, context):

        # Get objects to work with
        shapeObject = bpy.data.objects[self.objectName]
        armature = bpy.data.objects[self.armatureName]
        bone = armature.data.bones[self.boneName]

        # Rotate shape object to match bone local rotation in the armature.
        shapeObject.rotation_euler = (0.0, 0.0, 0.0)
        boneChain = bone.parent_recursive
        boneChain.insert(0, bone)
        for bi in boneChain:
            rotMat = Matrix((bi.x_axis, bi.y_axis, bi.z_axis)).transposed()
            shapeObject.rotation_euler.rotate(rotMat)

        # Same with translation and scaling.
        shapeObject.location = bone.head_local
        shapeObject.scale = bone.length * Vector((1.0, 1.0, 1.0))

        # Move object to armature coordinates system (except scaling, see below).
        rotMat = armature.rotation_euler
        shapeObject.rotation_euler.rotate(rotMat)
        shapeObject.location.rotate(rotMat)
        shapeObject.location += armature.location

        # Display warning message if the armature has scaling different to one.
        s = armature.scale
        if (s.x != 1) or (s.y != 1) or (s.z != 1):
            self.report({'Warning'}, "Armature should have a scale factor = 1 to match bone shape properly")

        return {'FINISHED'}

    def invoke(self, context, event):

        bone = bpy.context.active_pose_bone
        self.boneName = bpy.context.active_pose_bone.name
        self.objectName = bone.custom_shape.name
        self.armatureName = bpy.context.active_object.name

        # Open pop-up window for parameter setting.
        return context.window_manager.invoke_props_dialog(self)

if __name__ == '__main__':
    bpy.utils.register_class(FitBoneShapeOperator)


Old thread but my armature data panel may be what your looking for, click the link in my signature and enjoy, I will be updating it soon to be a bit more usefull to animators aswhell.