Skip to content
Snippets Groups Projects
finger.py 15.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • #====================== BEGIN GPL LICENSE BLOCK ======================
    #
    #  This program is free software; you can redistribute it and/or
    #  modify it under the terms of the GNU General Public License
    #  as published by the Free Software Foundation; either version 2
    #  of the License, or (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software Foundation,
    #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    #
    #======================= END GPL LICENSE BLOCK ========================
    
    import bpy
    from mathutils import Vector
    from rigify.utils import MetarigError
    from rigify.utils import copy_bone
    from rigify.utils import connected_children_names
    from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
    from rigify.utils import get_layers
    from rigify.utils import create_widget, create_line_widget, create_limb_widget
    from rna_prop_ui import rna_idprop_ui_prop_get
    import re
    
    
    class Rig:
        """ A finger rig.  It takes a single chain of bones.
            This is a control and deformation rig.
        """
        def __init__(self, obj, bone, params):
            """ Gather and validate data about the rig.
            """
            self.obj = obj
            self.org_bones = [bone] + connected_children_names(obj, bone)
            self.params = params
    
    
            if len(self.org_bones) <= 1:
                raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone)))
    
    
    
            # Get user-specified layers, if they exist
            if params.separate_extra_layers:
                self.ex_layers = list(params.extra_layers)
            else:
                self.ex_layers = None
    
            # Get other rig parameters
            self.primary_rotation_axis = params.primary_rotation_axis
            self.use_digit_twist = params.use_digit_twist
    
        def deform(self):
            """ Generate the deformation rig.
                Just a copy of the original bones, except the first digit which is a twist bone.
            """
            bpy.ops.object.mode_set(mode='EDIT')
    
            # Create the bones
            # First bone is a twist bone
            if self.use_digit_twist:
                b1a = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01")))
                b1b = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02")))
                b1tip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip")))
            else:
                b1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0])))
    
            # The rest are normal
            bones = []
            for bone in self.org_bones[1:]:
                bones += [copy_bone(self.obj, bone, make_deformer_name(strip_org(bone)))]
    
            # Position bones
            eb = self.obj.data.edit_bones
            if self.use_digit_twist:
                b1a_e = eb[b1a]
                b1b_e = eb[b1b]
                b1tip_e = eb[b1tip]
    
                b1tip_e.use_connect = False
                b1tip_e.tail += Vector((0.1, 0, 0))
                b1tip_e.head = b1b_e.tail
                b1tip_e.length = b1a_e.length / 4
    
                center = (b1a_e.head + b1a_e.tail) / 2
                b1a_e.tail = center
                b1b_e.use_connect = False
                b1b_e.head = center
    
            # Parenting
            if self.use_digit_twist:
                b1b_e.parent = eb[self.org_bones[0]]
                b1tip_e.parent = eb[self.org_bones[0]]
            else:
                eb[b1].use_connect = False
                eb[b1].parent = eb[self.org_bones[0]]
    
            for (ba, bb) in zip(bones, self.org_bones[1:]):
                eb[ba].use_connect = False
                eb[ba].parent = eb[bb]
    
            # Constraints
            if self.use_digit_twist:
                bpy.ops.object.mode_set(mode='OBJECT')
                pb = self.obj.pose.bones
    
                b1a_p = pb[b1a]
    
                con = b1a_p.constraints.new('COPY_LOCATION')
                con.name = "copy_location"
                con.target = self.obj
                con.subtarget = self.org_bones[0]
    
                con = b1a_p.constraints.new('COPY_SCALE')
                con.name = "copy_scale"
                con.target = self.obj
                con.subtarget = self.org_bones[0]
    
                con = b1a_p.constraints.new('DAMPED_TRACK')
                con.name = "track_to"
                con.target = self.obj
                con.subtarget = b1tip
    
        def control(self):
            """ Generate the control rig.
            """
            bpy.ops.object.mode_set(mode='EDIT')
    
            # Figure out the name for the control bone (remove the last .##)
            ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.org_bones[0])[::-1], count=1)[::-1]
    
            # Create the bones
            ctrl = copy_bone(self.obj, self.org_bones[0], ctrl_name)
    
            helpers = []
            bones = []
            for bone in self.org_bones:
                bones += [copy_bone(self.obj, bone, strip_org(bone))]
                helpers += [copy_bone(self.obj, bone, make_mechanism_name(strip_org(bone)))]
    
            # Position bones
            eb = self.obj.data.edit_bones
    
            length = 0.0
            for bone in helpers:
                length += eb[bone].length
                eb[bone].length /= 2
    
            eb[ctrl].length = length * 1.5
    
            # Parent bones
            prev = eb[self.org_bones[0]].parent
            for (b, h) in zip(bones, helpers):
                b_e = eb[b]
                h_e = eb[h]
                b_e.use_connect = False
                h_e.use_connect = False
    
                b_e.parent = h_e
                h_e.parent = prev
    
                prev = b_e
    
            # Transform locks and rotation mode
            bpy.ops.object.mode_set(mode='OBJECT')
            pb = self.obj.pose.bones
    
            for bone in bones[1:]:
                pb[bone].lock_location = True, True, True
    
            if pb[self.org_bones[0]].bone.use_connect == True:
                pb[bones[0]].lock_location = True, True, True
    
            pb[ctrl].lock_scale = True, False, True
    
            for bone in helpers:
                pb[bone].rotation_mode = 'XYZ'
    
            # Drivers
            i = 1
            val = 1.2 / (len(self.org_bones) - 1)
            for bone in helpers:
                # Add custom prop
                prop_name = "bend_%02d" % i
                prop = rna_idprop_ui_prop_get(pb[ctrl], prop_name, create=True)
                prop["min"] = 0.0
                prop["max"] = 1.0
                prop["soft_min"] = 0.0
                prop["soft_max"] = 1.0
                if i == 1:
                    pb[ctrl][prop_name] = 0.0
                else:
                    pb[ctrl][prop_name] = val
    
                # Add driver
                if 'X' in self.primary_rotation_axis:
                    fcurve = pb[bone].driver_add("rotation_euler", 0)
                elif 'Y' in self.primary_rotation_axis:
                    fcurve = pb[bone].driver_add("rotation_euler", 1)
                else:
                    fcurve = pb[bone].driver_add("rotation_euler", 2)
    
                driver = fcurve.driver
                driver.type = 'SCRIPTED'
    
                var = driver.variables.new()
                var.name = "ctrl_y"
                var.targets[0].id_type = 'OBJECT'
                var.targets[0].id = self.obj
                var.targets[0].data_path = pb[ctrl].path_from_id() + '.scale[1]'
    
                var = driver.variables.new()
                var.name = "bend"
                var.targets[0].id_type = 'OBJECT'
                var.targets[0].id = self.obj
                var.targets[0].data_path = pb[ctrl].path_from_id() + '["' + prop_name + '"]'
    
                if '-' in self.primary_rotation_axis:
                    driver.expression = "-(1.0-ctrl_y) * bend * 3.14159 * 2"
                else:
                    driver.expression = "(1.0-ctrl_y) * bend * 3.14159 * 2"
    
                i += 1
    
            # Constraints
            con = pb[helpers[0]].constraints.new('COPY_LOCATION')
            con.name = "copy_location"
            con.target = self.obj
            con.subtarget = ctrl
    
            con = pb[helpers[0]].constraints.new('COPY_ROTATION')
            con.name = "copy_rotation"
            con.target = self.obj
            con.subtarget = ctrl
    
            # Constrain org bones to the control bones
            for (bone, org) in zip(bones, self.org_bones):
                con = pb[org].constraints.new('COPY_TRANSFORMS')
                con.name = "copy_transforms"
                con.target = self.obj
                con.subtarget = bone
    
            # Set layers for extra control bones
            if self.ex_layers:
                for bone in bones:
                    pb[bone].bone.layers = self.ex_layers
    
            # Create control widgets
            w = create_widget(self.obj, ctrl)
            if w != None:
                mesh = w.data
                verts = [(0, 0, 0), (0, 1, 0), (0.05, 1, 0), (0.05, 1.1, 0), (-0.05, 1.1, 0), (-0.05, 1, 0)]
                if 'Z' in self.primary_rotation_axis:
                    # Flip x/z coordinates
                    temp = []
                    for v in verts:
                        temp += [(v[2], v[1], v[0])]
                    verts = temp
                edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
                mesh.from_pydata(verts, edges, [])
                mesh.update()
    
            for bone in bones:
                create_limb_widget(self.obj, bone)
    
        def generate(self):
            """ Generate the rig.
                Do NOT modify any of the original bones, except for adding constraints.
                The main armature should be selected and active before this is called.
            """
            self.deform()
            self.control()
    
        @classmethod
        def add_parameters(self, group):
            """ Add the parameters of this rig type to the
                RigifyParameters IDPropertyGroup
            """
            items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')]
            group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X')
    
            group.separate_extra_layers = bpy.props.BoolProperty(name="Separate Secondary Control Layers:", default=False, description="Enable putting the secondary controls on a separate layer from the primary controls.")
            group.extra_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the secondary controls to be on.")
    
            group.use_digit_twist = bpy.props.BoolProperty(name="Digit Twist", default=True, description="Generate the dual-bone twist setup for the first finger digit.")
    
        @classmethod
        def parameters_ui(self, layout, obj, bone):
            """ Create the ui for the rig parameters.
            """
            params = obj.pose.bones[bone].rigify_parameters[0]
    
            r = layout.row()
            r.prop(params, "separate_extra_layers")
    
            r = layout.row()
            r.active = params.separate_extra_layers
    
            col = r.column(align=True)
            row = col.row(align=True)
            row.prop(params, "extra_layers", index=0, toggle=True, text="")
            row.prop(params, "extra_layers", index=1, toggle=True, text="")
            row.prop(params, "extra_layers", index=2, toggle=True, text="")
            row.prop(params, "extra_layers", index=3, toggle=True, text="")
            row.prop(params, "extra_layers", index=4, toggle=True, text="")
            row.prop(params, "extra_layers", index=5, toggle=True, text="")
            row.prop(params, "extra_layers", index=6, toggle=True, text="")
            row.prop(params, "extra_layers", index=7, toggle=True, text="")
            row = col.row(align=True)
            row.prop(params, "extra_layers", index=16, toggle=True, text="")
            row.prop(params, "extra_layers", index=17, toggle=True, text="")
            row.prop(params, "extra_layers", index=18, toggle=True, text="")
            row.prop(params, "extra_layers", index=19, toggle=True, text="")
            row.prop(params, "extra_layers", index=20, toggle=True, text="")
            row.prop(params, "extra_layers", index=21, toggle=True, text="")
            row.prop(params, "extra_layers", index=22, toggle=True, text="")
            row.prop(params, "extra_layers", index=23, toggle=True, text="")
    
            col = r.column(align=True)
            row = col.row(align=True)
            row.prop(params, "ik_layers", index=8, toggle=True, text="")
            row.prop(params, "ik_layers", index=9, toggle=True, text="")
            row.prop(params, "ik_layers", index=10, toggle=True, text="")
            row.prop(params, "ik_layers", index=11, toggle=True, text="")
            row.prop(params, "ik_layers", index=12, toggle=True, text="")
            row.prop(params, "ik_layers", index=13, toggle=True, text="")
            row.prop(params, "ik_layers", index=14, toggle=True, text="")
            row.prop(params, "ik_layers", index=15, toggle=True, text="")
            row = col.row(align=True)
            row.prop(params, "ik_layers", index=24, toggle=True, text="")
            row.prop(params, "ik_layers", index=25, toggle=True, text="")
            row.prop(params, "ik_layers", index=26, toggle=True, text="")
            row.prop(params, "ik_layers", index=27, toggle=True, text="")
            row.prop(params, "ik_layers", index=28, toggle=True, text="")
            row.prop(params, "ik_layers", index=29, toggle=True, text="")
            row.prop(params, "ik_layers", index=30, toggle=True, text="")
            row.prop(params, "ik_layers", index=31, toggle=True, text="")
    
            r = layout.row()
            r.label(text="Bend rotation axis:")
            r.prop(params, "primary_rotation_axis", text="")
    
            col = layout.column()
            col.prop(params, "use_digit_twist")
    
        @classmethod
        def create_sample(self, obj):
            # generated by rigify.utils.write_metarig
            bpy.ops.object.mode_set(mode='EDIT')
            arm = obj.data
    
            bones = {}
    
            bone = arm.edit_bones.new('finger.01')
            bone.head[:] = 0.0000, 0.0000, 0.0000
            bone.tail[:] = 0.2529, 0.0000, 0.0000
            bone.roll = 3.1416
            bone.use_connect = False
            bones['finger.01'] = bone.name
            bone = arm.edit_bones.new('finger.02')
            bone.head[:] = 0.2529, 0.0000, 0.0000
            bone.tail[:] = 0.4024, 0.0000, -0.0264
            bone.roll = -2.9671
            bone.use_connect = True
            bone.parent = arm.edit_bones[bones['finger.01']]
            bones['finger.02'] = bone.name
            bone = arm.edit_bones.new('finger.03')
            bone.head[:] = 0.4024, 0.0000, -0.0264
            bone.tail[:] = 0.4975, -0.0000, -0.0610
            bone.roll = -2.7925
            bone.use_connect = True
            bone.parent = arm.edit_bones[bones['finger.02']]
            bones['finger.03'] = bone.name
    
            bpy.ops.object.mode_set(mode='OBJECT')
            pbone = obj.pose.bones[bones['finger.01']]
            pbone.rigify_type = 'finger'
            pbone.lock_location = (True, True, True)
            pbone.lock_rotation = (False, False, False)
            pbone.lock_rotation_w = False
            pbone.lock_scale = (False, False, False)
            pbone.rotation_mode = 'YZX'
            pbone.rigify_parameters.add()
            pbone = obj.pose.bones[bones['finger.02']]
            pbone.rigify_type = ''
            pbone.lock_location = (False, False, False)
            pbone.lock_rotation = (False, False, False)
            pbone.lock_rotation_w = False
            pbone.lock_scale = (False, False, False)
            pbone.rotation_mode = 'YZX'
            pbone = obj.pose.bones[bones['finger.03']]
            pbone.rigify_type = ''
            pbone.lock_location = (False, False, False)
            pbone.lock_rotation = (False, False, False)
            pbone.lock_rotation_w = False
            pbone.lock_scale = (False, False, False)
            pbone.rotation_mode = 'YZX'
    
            bpy.ops.object.mode_set(mode='EDIT')
            for bone in arm.edit_bones:
                bone.select = False
                bone.select_head = False
                bone.select_tail = False
            for b in bones:
                bone = arm.edit_bones[bones[b]]
                bone.select = True
                bone.select_head = True
                bone.select_tail = True
                arm.edit_bones.active = bone