Rigify: convert the palm rig and support controls for both sides.

This commit is contained in:
Alexander Gavrilov 2019-09-15 20:00:05 +03:00
parent 9a9923dd27
commit d860b3c7e2
2 changed files with 181 additions and 126 deletions

View File

@ -1748,6 +1748,10 @@ def create(obj):
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
try:
pbone.rigify_parameters.palm_both_sides = True
except AttributeError:
pass
pbone = obj.pose.bones[bones['r_palm.002.L']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
@ -1765,7 +1769,6 @@ def create(obj):
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
pbone = obj.pose.bones[bones['r_palm.004.L']]
pbone.rigify_type = 'limbs.super_palm'
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
@ -1780,6 +1783,10 @@ def create(obj):
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
try:
pbone.rigify_parameters.palm_both_sides = True
except AttributeError:
pass
pbone = obj.pose.bones[bones['r_palm.002.R']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
@ -1797,7 +1804,6 @@ def create(obj):
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
pbone = obj.pose.bones[bones['r_palm.004.R']]
pbone.rigify_type = 'limbs.super_palm'
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
@ -2313,7 +2319,6 @@ def create(obj):
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
pbone = obj.pose.bones[bones['f_palm.004.L']]
pbone.rigify_type = 'limbs.super_palm'
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
@ -2328,6 +2333,10 @@ def create(obj):
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
try:
pbone.rigify_parameters.palm_both_sides = True
except AttributeError:
pass
pbone = obj.pose.bones[bones['f_palm.002.L']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
@ -2345,7 +2354,6 @@ def create(obj):
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
pbone = obj.pose.bones[bones['f_palm.004.R']]
pbone.rigify_type = 'limbs.super_palm'
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
@ -2360,6 +2368,10 @@ def create(obj):
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone.bone.layers = [False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
try:
pbone.rigify_parameters.palm_both_sides = True
except AttributeError:
pass
pbone = obj.pose.bones[bones['f_palm.002.R']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)

View File

@ -18,21 +18,22 @@
# <pep8 compliant>
import re
from math import cos, pi
import bpy
import re
from ...utils import MetarigError
from ...utils import copy_bone
from ...utils import strip_org, deformer
from ...utils import create_widget
from math import cos, pi
from itertools import count, repeat
from rigify.utils.naming import strip_org, make_derived_name
from rigify.utils.widgets import create_widget
from rigify.utils.misc import map_list
from rigify.base_rig import BaseRig, stage
def bone_siblings(obj, bone):
""" Returns a list of the siblings of the given bone.
This requires that the bones has a parent.
"""
parent = obj.data.bones[bone].parent
@ -48,133 +49,92 @@ def bone_siblings(obj, bone):
return bones
def bone_distance(obj, bone1, bone2):
""" Returns the distance between two bones.
"""
vec = obj.data.bones[bone1].head - obj.data.bones[bone2].head
return vec.length
class Rig:
class Rig(BaseRig):
""" A "palm" rig. A set of sibling bones that bend with each other.
This is a control and deformation rig.
"""
def __init__(self, obj, bone, params):
""" Gather and validate data about the rig.
"""
self.obj = obj
self.params = params
siblings = bone_siblings(obj, bone)
if len(siblings) == 0:
raise MetarigError(
"RIGIFY ERROR: Bone '%s': must have a parent and at least one sibling" %
(strip_org(bone)))
def find_org_bones(self, bone):
base_head = bone.bone.head
siblings = bone_siblings(self.obj, bone.name)
# Sort list by name and distance
siblings.sort()
siblings.sort(key=lambda b: bone_distance(obj, bone, b))
siblings.sort(key=lambda b: (self.get_bone(b).bone.head - base_head).length)
self.org_bones = [bone] + siblings
return [bone.name] + siblings
# Get rig parameters
self.palm_rotation_axis = params.palm_rotation_axis
def initialize(self):
if len(self.bones.org) <= 1:
self.raise_error('The palm rig must have a parent and at least one sibling')
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.palm_rotation_axis = self.params.palm_rotation_axis
self.make_secondary = self.params.palm_both_sides
"""
bpy.ops.object.mode_set(mode='EDIT')
self.order = 'YXZ' if 'X' in self.palm_rotation_axis else 'YZX'
# Figure out the name for the control bone (remove the last .##)
last_bone = self.org_bones[-1:][0]
ctrl_name = re.sub("([0-9]+\.)", "", strip_org(last_bone)[::-1], count=1)[::-1]
self.ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.bones.org[-1])[::-1], count=1)[::-1]
# Make control bone
ctrl = copy_bone(self.obj, last_bone, ctrl_name)
def parent_bones(self):
self.rig_parent_bone = self.get_bone_parent(self.bones.org[0])
# Make deformation bones
def_bones = []
for bone in self.org_bones:
b = copy_bone(self.obj, bone, deformer(strip_org(bone)))
def_bones += [b]
# Parent to the deform bone of the parent if exists
def_bone = make_derived_name(self.rig_parent_bone, 'def')
# Parenting
eb = self.obj.data.edit_bones
if def_bone in self.obj.data.bones:
self.rig_parent_bone = def_bone
# turn off inherit scale for all ORG-bones to prevent undesired transformations
####################################################
# BONES
#
# org[]:
# Original bones in order of distance.
# ctrl:
# master:
# Main control.
# secondary:
# Control for the other side.
# deform[]:
# DEF bones
#
####################################################
for o in self.org_bones:
eb[o].inherit_scale = 'NONE'
####################################################
# Master control
for d, b in zip(def_bones, self.org_bones):
eb[d].use_connect = False
eb[d].parent = eb[b]
@stage.generate_bones
def make_master_control(self):
orgs = self.bones.org
# Get ORG parent bone
org_parent = eb[self.org_bones[0]].parent.name
self.bones.ctrl.master = self.copy_bone(orgs[-1], self.ctrl_name, parent=True)
# Get DEF parent from ORG parent
def_parent = deformer(strip_org(org_parent))
if self.make_secondary:
second_name = make_derived_name(orgs[0], 'ctrl')
self.bones.ctrl.secondary = self.copy_bone(orgs[0], second_name, parent=True)
# Switch parent
if def_parent in eb.keys():
parent_to = def_parent
else:
parent_to = org_parent
for o in self.org_bones:
eb[o].parent = eb[parent_to]
eb[ctrl].parent = eb[parent_to]
@stage.parent_bones
def parent_master_control(self):
self.set_bone_parent(self.bones.ctrl.master, self.rig_parent_bone)
# Constraints
bpy.ops.object.mode_set(mode='OBJECT')
pb = self.obj.pose.bones
if self.make_secondary:
self.set_bone_parent(self.bones.ctrl.secondary, self.rig_parent_bone)
i = 0
div = len(self.org_bones) - 1
for b in self.org_bones:
con = pb[b].constraints.new('COPY_TRANSFORMS')
con.name = "copy_transforms"
con.target = self.obj
con.subtarget = ctrl
con.target_space = 'LOCAL'
con.owner_space = 'LOCAL'
con.influence = i / div
@stage.configure_bones
def configure_master_control(self):
self.copy_bone_properties(self.bones.org[-1], self.bones.ctrl.master)
con = pb[b].constraints.new('COPY_SCALE')
con.name = "copy_scale"
con.target = self.obj
con.subtarget = parent_to
con.target_space = 'WORLD'
con.owner_space = 'WORLD'
con.influence = 1
if self.make_secondary:
self.copy_bone_properties(self.bones.org[0], self.bones.ctrl.secondary)
con = pb[b].constraints.new('COPY_ROTATION')
con.name = "copy_rotation"
con.target = self.obj
con.subtarget = ctrl
con.target_space = 'LOCAL'
con.owner_space = 'LOCAL'
if 'X' in self.palm_rotation_axis:
con.invert_x = True
con.use_x = True
con.use_z = False
else:
con.invert_z = True
con.use_x = False
con.use_z = True
con.use_y = False
@stage.generate_widgets
def make_master_control_widgets(self):
self.make_control_widget(self.bones.ctrl.master)
con.influence = (i / div) - (1 - cos((i * pi / 2) / div))
if self.make_secondary:
self.make_control_widget(self.bones.ctrl.secondary)
i += 1
# Create control widget
def make_control_widget(self, ctrl):
w = create_widget(self.obj, ctrl)
if w is not None:
mesh = w.data
@ -213,27 +173,110 @@ class Rig:
mod = w.modifiers.new("subsurf", 'SUBSURF')
mod.levels = 2
####################################################
# ORG bones
def add_parameters(params):
""" Add the parameters of this rig type to the
RigifyParameters PropertyGroup
@stage.parent_bones
def parent_org_chain(self):
for org in self.bones.org:
self.set_bone_parent(org, self.rig_parent_bone)
self.get_bone(org).inherit_scale = 'NONE'
"""
items = [('X', 'X', ''), ('Z', 'Z', '')]
params.palm_rotation_axis = bpy.props.EnumProperty(
items=items,
name="Palm Rotation Axis",
default='X',
@stage.rig_bones
def rig_org_chain(self):
orgs = self.bones.org
ctrl = self.bones.ctrl
for args in zip(count(0), orgs, repeat(len(orgs))):
self.rig_org_bone(*args)
def rig_org_bone(self, i, org, num_orgs):
ctrl = self.bones.ctrl
fac = i / (num_orgs - 1)
if fac > 0:
self.make_constraint(
org, 'COPY_TRANSFORMS', ctrl.master, space='LOCAL',
influence=fac
)
if self.make_secondary and fac < 1:
self.make_constraint(
org, 'COPY_LOCATION', ctrl.secondary, space='LOCAL',
use_offset=True, influence=1-fac
)
self.make_constraint(
org, 'COPY_ROTATION', ctrl.secondary, space='LOCAL',
euler_order=self.order, mix_mode='ADD', influence=1-fac
)
def parameters_ui(layout, params):
""" Create the ui for the rig parameters.
elif self.make_secondary:
self.make_constraint(
org, 'COPY_TRANSFORMS', ctrl.secondary, space='LOCAL'
)
"""
r = layout.row()
r.label(text="Primary rotation axis:")
r.prop(params, "palm_rotation_axis", text="")
self.make_constraint(org, 'COPY_SCALE', self.rig_parent_bone)
self.rig_org_back_rotation(org, ctrl.master, fac)
if self.make_secondary:
self.rig_org_back_rotation(org, ctrl.secondary, 1 - fac)
def rig_org_back_rotation(self, org, ctrl, fac):
if 0 < fac < 1:
inf = (fac + 1) * (fac + cos(fac * pi / 2) - 1)
if 'X' in self.palm_rotation_axis:
self.make_constraint(
org, 'COPY_ROTATION', ctrl, space='LOCAL',
invert_x=True, use_xyz=(True,False,False),
euler_order=self.order, mix_mode='ADD', influence=inf
)
else:
self.make_constraint(
org, 'COPY_ROTATION', ctrl, space='LOCAL',
invert_z=True, use_xyz=(False,False,True),
euler_order=self.order, mix_mode='ADD', influence=inf
)
####################################################
# DEF bones
@stage.generate_bones
def make_def_chain(self):
self.bones.deform = map_list(self.make_deform_bone, self.bones.org)
def make_deform_bone(self, org):
return self.copy_bone(org, make_derived_name(org, 'def'))
@stage.parent_bones
def parent_deform_chain(self):
for deform, org in zip(self.bones.deform, self.bones.org):
self.set_bone_parent(deform, org, use_connect=False)
####################################################
# Settings
@classmethod
def add_parameters(cls, params):
items = [('X', 'X', ''), ('Z', 'Z', '')]
params.palm_rotation_axis = bpy.props.EnumProperty(
items=items,
name="Palm Rotation Axis",
default='X',
)
params.palm_both_sides = bpy.props.BoolProperty(
name="Both Sides",
default=False,
description="Create controls for both sides of the palm"
)
@classmethod
def parameters_ui(cls, layout, params):
r = layout.row()
r.label(text="Primary rotation axis:")
r.prop(params, "palm_rotation_axis", text="")
layout.prop(params, "palm_both_sides")
def create_sample(obj):