diff options
Diffstat (limited to 'rigify/rigs')
-rw-r--r-- | rigify/rigs/basic/copy_chain.py | 33 | ||||
-rw-r--r-- | rigify/rigs/basic/pivot.py | 236 | ||||
-rw-r--r-- | rigify/rigs/basic/super_copy.py | 108 | ||||
-rw-r--r-- | rigify/rigs/chain_rigs.py | 6 | ||||
-rw-r--r-- | rigify/rigs/experimental/super_chain.py | 8 | ||||
-rw-r--r-- | rigify/rigs/limbs/arm.py | 69 | ||||
-rw-r--r-- | rigify/rigs/limbs/leg.py | 190 | ||||
-rw-r--r-- | rigify/rigs/limbs/limb_rigs.py | 97 | ||||
-rw-r--r-- | rigify/rigs/limbs/paw.py | 8 | ||||
-rw-r--r-- | rigify/rigs/limbs/simple_tentacle.py | 2 | ||||
-rw-r--r-- | rigify/rigs/limbs/super_finger.py | 42 | ||||
-rw-r--r-- | rigify/rigs/limbs/super_palm.py | 3 | ||||
-rw-r--r-- | rigify/rigs/spines/basic_spine.py | 145 | ||||
-rw-r--r-- | rigify/rigs/spines/basic_tail.py | 7 | ||||
-rw-r--r-- | rigify/rigs/spines/spine_rigs.py | 60 | ||||
-rw-r--r-- | rigify/rigs/spines/super_head.py | 5 | ||||
-rw-r--r-- | rigify/rigs/spines/super_spine.py | 4 |
17 files changed, 851 insertions, 172 deletions
diff --git a/rigify/rigs/basic/copy_chain.py b/rigify/rigs/basic/copy_chain.py index 5145d735..a3920ced 100644 --- a/rigify/rigs/basic/copy_chain.py +++ b/rigify/rigs/basic/copy_chain.py @@ -22,9 +22,10 @@ import bpy from ..chain_rigs import SimpleChainRig +from ...utils.layers import DEF_LAYER from ...utils.errors import MetarigError from ...utils.rig import connected_children_names -from ...utils.naming import strip_org, make_deformer_name +from ...utils.naming import make_derived_name from ...utils.widgets_basic import create_bone_widget from ...base_rig import BaseRig, stage @@ -41,7 +42,12 @@ class Rig(SimpleChainRig): """ Gather and validate data about the rig. """ self.make_controls = self.params.make_controls - self.make_deforms = self.params.make_deforms + + deform = self.params.make_deforms + rename = self.params.rename_to_deform + + self.make_deforms = deform and not rename + self.rename_deforms = deform and rename ############################## # Control chain @@ -92,6 +98,20 @@ class Rig(SimpleChainRig): if self.make_deforms: super().rig_deform_chain() + ############################## + # Rename To Deform + + def finalize(self): + if self.rename_deform: + new_names = [ self.rename_bone(name, make_derived_name(name, 'def')) for name in self.bones.org ] + + for name in new_names: + bone = self.get_bone(name).bone + bone.use_deform = True + bone.layers = DEF_LAYER + + ############################## + # Parameter UI @classmethod def add_parameters(self, params): @@ -101,6 +121,11 @@ class Rig(SimpleChainRig): params.make_controls = bpy.props.BoolProperty(name="Controls", default=True, description="Create control bones for the copy") params.make_deforms = bpy.props.BoolProperty(name="Deform", default=True, description="Create deform bones for the copy") + params.rename_to_deform = bpy.props.BoolProperty( + name = "Rename To Deform", + default = False, + description = "Rename the original bone itself to use as deform bone (advanced feature)" + ) @classmethod def parameters_ui(self, layout, params): @@ -111,6 +136,10 @@ class Rig(SimpleChainRig): r = layout.row() r.prop(params, "make_deforms") + if params.make_deforms: + r = layout.row() + r.prop(params, "rename_to_deform") + def create_sample(obj): """ Create a sample metarig for this rig type. diff --git a/rigify/rigs/basic/pivot.py b/rigify/rigs/basic/pivot.py new file mode 100644 index 00000000..e5d31659 --- /dev/null +++ b/rigify/rigs/basic/pivot.py @@ -0,0 +1,236 @@ +#====================== 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 ======================== + +# <pep8 compliant> + +import bpy + +from ...base_rig import BaseRig + +from ...utils.naming import make_derived_name +from ...utils.bones import set_bone_widget_transform +from ...utils.widgets_basic import create_cube_widget, create_pivot_widget +from ...utils.switch_parent import SwitchParentBuilder + + +class Rig(BaseRig): + """ A rig providing a rotation pivot control that can be moved. """ + def find_org_bones(self, pose_bone): + return pose_bone.name + + + def initialize(self): + self.make_control = self.params.make_extra_control + self.make_pivot = self.params.make_control or not self.make_control + self.make_deform = self.params.make_extra_deform + + + def generate_bones(self): + org = self.bones.org + + if self.make_control: + self.bones.ctrl.master = name = self.copy_bone(org, make_derived_name(org, 'ctrl'), parent=True) + + if self.make_pivot: + self.bones.ctrl.pivot = self.copy_bone(org, make_derived_name(org, 'ctrl', '_pivot')) + + if self.params.make_parent_switch: + self.build_parent_switch(name) + + if self.params.register_parent: + self.register_parent(name, self.get_parent_tags()) + + else: + self.bones.ctrl.pivot = self.copy_bone(org, make_derived_name(org, 'ctrl'), parent=True) + + if self.make_deform: + self.bones.deform = self.copy_bone(org, make_derived_name(org, 'def'), bbone=True) + + + def build_parent_switch(self, master_name): + pbuilder = SwitchParentBuilder(self.generator) + + org_parent = self.get_bone_parent(self.bones.org) + parents = [org_parent] if org_parent else [] + + pbuilder.build_child( + self, master_name, + context_rig=self.rigify_parent, allow_self=True, + prop_name="Parent ({})".format(master_name), + extra_parents=parents, select_parent=org_parent, + controls=lambda: self.bones.ctrl.flatten() + ) + + def get_parent_tags(self): + tags = {t.strip() for t in self.params.register_parent_tags.split(',')} + + if self.params.make_parent_switch: + tags.add('child') + + tags.discard('') + return tags + + def register_parent(self, master_name, tags): + pbuilder = SwitchParentBuilder(self.generator) + + inject = self.rigify_parent if 'injected' in tags else None + + pbuilder.register_parent( + self, self.bones.org, name=master_name, + inject_into=inject, tags=tags + ) + + + def parent_bones(self): + ctrl = self.bones.ctrl + + if self.make_pivot: + if self.make_control: + self.set_bone_parent(ctrl.pivot, ctrl.master, use_connect=False) + + self.set_bone_parent(self.bones.org, ctrl.pivot, use_connect=False) + + else: + self.set_bone_parent(self.bones.org, ctrl.master, use_connect=False) + + if self.make_deform: + self.set_bone_parent(self.bones.deform, self.bones.org, use_connect=False) + + + def configure_bones(self): + if self.make_control: + self.copy_bone_properties(self.bones.org, self.bones.ctrl.master) + + else: + self.copy_bone_properties(self.bones.org, self.bones.ctrl.pivot) + + + def rig_bones(self): + if self.make_pivot: + self.make_constraint( + self.bones.org, 'COPY_LOCATION', self.bones.ctrl.pivot, + space='LOCAL', invert_xyz=(True,)*3 + ) + + + def generate_widgets(self): + if self.make_pivot: + create_pivot_widget(self.obj, self.bones.ctrl.pivot, square=True, axis_size=2.0) + + if self.make_control: + set_bone_widget_transform(self.obj, self.bones.ctrl.master, self.bones.org) + + create_cube_widget(self.obj, self.bones.ctrl.master, radius=0.5) + + + @classmethod + def add_parameters(self, params): + params.make_control = bpy.props.BoolProperty( + name = "Control", + default = True, + description = "Create a control bone for the copy" + ) + + params.make_parent_switch = bpy.props.BoolProperty( + name = "Switchable Parent", + default = False, + description = "Allow switching the parent of the master control" + ) + + params.register_parent = bpy.props.BoolProperty( + name = "Register Parent", + default = False, + description = "Register the control as a switchable parent candidate" + ) + + params.register_parent_tags = bpy.props.StringProperty( + name = "Parent Tags", + default = "", + description = "Comma-separated tags to use for the registered parent" + ) + + params.make_extra_control = bpy.props.BoolProperty( + name = "Extra Control", + default = False, + description = "Create an optional control" + ) + + params.make_extra_deform = bpy.props.BoolProperty( + name = "Extra Deform", + default = False, + description = "Create an optional deform bone" + ) + + + @classmethod + def parameters_ui(self, layout, params): + r = layout.row() + r.prop(params, "make_extra_control", text="Master Control") + + if params.make_extra_control: + layout.prop(params, "make_parent_switch") + layout.prop(params, "register_parent") + + r = layout.row() + r.active = params.register_parent + r.prop(params, "register_parent_tags", text="Tags") + + layout.prop(params, "make_control", text="Pivot Control") + + r = layout.row() + r.prop(params, "make_extra_deform", text="Deform Bone") + + +def create_sample(obj): + """ Create a sample metarig for this rig type. + """ + # generated by rigify.utils.write_metarig + bpy.ops.object.mode_set(mode='EDIT') + arm = obj.data + + bones = {} + + bone = arm.edit_bones.new('pivot') + bone.head[:] = 0.0000, 0.0000, 0.0000 + bone.tail[:] = 0.0000, 0.5000, 0.0000 + bone.roll = 0.0000 + bone.use_connect = False + bones['pivot'] = bone.name + + bpy.ops.object.mode_set(mode='OBJECT') + pbone = obj.pose.bones[bones['pivot']] + pbone.rigify_type = 'basic.pivot' + 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 = 'QUATERNION' + + 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 + + return bones diff --git a/rigify/rigs/basic/super_copy.py b/rigify/rigs/basic/super_copy.py index 5abbf22e..f55dce68 100644 --- a/rigify/rigs/basic/super_copy.py +++ b/rigify/rigs/basic/super_copy.py @@ -22,9 +22,12 @@ import bpy from ...base_rig import BaseRig +from ...utils.layers import DEF_LAYER from ...utils.naming import strip_org, make_deformer_name from ...utils.widgets_basic import create_bone_widget, create_circle_widget +from itertools import repeat + class Rig(BaseRig): """ A "copy" rig. All it does is duplicate the original bone and @@ -43,7 +46,14 @@ class Rig(BaseRig): self.make_control = self.params.make_control self.make_widget = self.params.make_widget - self.make_deform = self.params.make_deform + + deform = self.params.make_deform + rename = self.params.rename_to_deform + + self.make_deform = deform and not rename + self.rename_deform = deform and rename + + self.relink = self.params.relink_constraints def generate_bones(self): @@ -64,6 +74,18 @@ class Rig(BaseRig): if self.make_deform: self.set_bone_parent(bones.deform, bones.org, use_connect=False) + if self.relink: + self.generator.disable_auto_parent(bones.org) + + parent_spec = self.params.parent_bone + if parent_spec: + old_parent = self.get_bone_parent(bones.org) + new_parent = self.find_relink_target(parent_spec, old_parent or '') or None + self.set_bone_parent(bones.org, new_parent) + + if self.make_control: + self.set_bone_parent(bones.ctrl, new_parent) + def configure_bones(self): bones = self.bones @@ -75,9 +97,53 @@ class Rig(BaseRig): def rig_bones(self): bones = self.bones + if self.relink: + for con in self.get_bone(bones.org).constraints: + parts = con.name.split('@') + + if len(parts) > 1: + self.relink_constraint(con, parts[1:]) + if self.make_control: # Constrain the original bone. - self.make_constraint(bones.org, 'COPY_TRANSFORMS', bones.ctrl) + self.make_constraint(bones.org, 'COPY_TRANSFORMS', bones.ctrl, insert_index=0) + + def relink_constraint(self, con, specs): + if con.type == 'ARMATURE': + if len(specs) == 1: + specs = repeat(specs[0]) + elif len(specs) != len(con.specs): + self.report_error("Constraint {} actually has {} targets", con.name, len(con.targets)) + + for tgt, spec in zip(con.targets, specs): + tgt.subtarget = self.find_relink_target(spec, tgt.subtarget) + + else: + if len(specs) > 1: + self.report_error("Only the Armature constraint can have multiple '@' targets: {}", con.name) + + con.subtarget = self.find_relink_target(specs[0], con.subtarget) + + def find_relink_target(self, spec, old_target): + if spec == '': + return old_target + elif spec in {'DEF', 'MCH'}: + spec = spec + '-' + strip_org(old_target) + + if spec not in self.obj.pose.bones: + # Hack: allow referring to copy rigs using Rename To Deform as DEF + if old_target.startswith('ORG-') and spec == make_deformer_name(strip_org(old_target)): + from . import copy_chain + + owner = self.generator.bone_owners.get(old_target) + + if ((isinstance(owner, Rig) and owner.rename_deform) or + (isinstance(owner, copy_chain.Rig) and owner.rename_deforms)): + return old_target + + self.report_error("Cannot find bone '{}' for relinking", spec) + + return spec def generate_widgets(self): @@ -91,6 +157,15 @@ class Rig(BaseRig): create_bone_widget(self.obj, bones.ctrl) + def finalize(self): + if self.rename_deform: + new_name = self.rename_bone(self.bones.org, make_deformer_name(self.org_name)) + + bone = self.get_bone(new_name).bone + bone.use_deform = True + bone.layers = DEF_LAYER + + @classmethod def add_parameters(self, params): """ Add the parameters of this rig type to the @@ -114,6 +189,24 @@ class Rig(BaseRig): description = "Create a deform bone for the copy" ) + params.rename_to_deform = bpy.props.BoolProperty( + name = "Rename To Deform", + default = False, + description = "Rename the original bone itself to use as deform bone (advanced feature)" + ) + + params.relink_constraints = bpy.props.BoolProperty( + name = "Relink Constraints", + default = False, + description = "For constraints with names formed like 'base@bonename', use the part after '@' as the new subtarget after all bones are created. Use '@DEF' or '@MCH' to simply prepend the prefix" + ) + + params.parent_bone = bpy.props.StringProperty( + name = "Parent", + default = "", + description = "Replace the parent with a different bone after all bones are created. Using simply DEF or MCH will prepend the prefix instead" + ) + @classmethod def parameters_ui(self, layout, params): @@ -127,6 +220,17 @@ class Rig(BaseRig): r = layout.row() r.prop(params, "make_deform") + if params.make_deform: + r = layout.row() + r.prop(params, "rename_to_deform") + + r = layout.row() + r.prop(params, "relink_constraints") + + if params.relink_constraints: + r = layout.row() + r.prop(params, "parent_bone") + def create_sample(obj): """ Create a sample metarig for this rig type. diff --git a/rigify/rigs/chain_rigs.py b/rigify/rigs/chain_rigs.py index 3f53cd69..fc070eb1 100644 --- a/rigify/rigs/chain_rigs.py +++ b/rigify/rigs/chain_rigs.py @@ -82,10 +82,10 @@ class SimpleChainRig(BaseRig): @stage.generate_widgets def make_control_widgets(self): - for ctrl in self.bones.ctrl.fk: - self.make_control_widget(ctrl) + for args in zip(count(0), self.bones.ctrl.fk): + self.make_control_widget(*args) - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_bone_widget(self.obj, ctrl) ############################## diff --git a/rigify/rigs/experimental/super_chain.py b/rigify/rigs/experimental/super_chain.py index df0facdf..3f7ca5d6 100644 --- a/rigify/rigs/experimental/super_chain.py +++ b/rigify/rigs/experimental/super_chain.py @@ -714,10 +714,10 @@ def add_parameters(params): ) params.bbones = bpy.props.IntProperty( - name='bbone segments', - default=10, - min=1, - description='Number of segments' + name = 'B-Bone Segments', + default = 10, + min = 1, + description = 'Number of B-Bone segments' ) params.wgt_offset = bpy.props.FloatProperty( diff --git a/rigify/rigs/limbs/arm.py b/rigify/rigs/limbs/arm.py index 98a3c50f..e79edc5c 100644 --- a/rigify/rigs/limbs/arm.py +++ b/rigify/rigs/limbs/arm.py @@ -21,12 +21,15 @@ import bpy from itertools import count +from mathutils import Matrix -from ...utils.bones import BoneDict, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis +from ...utils.bones import put_bone, compute_chain_x_axis, align_bone_x_axis, align_bone_z_axis from ...utils.naming import make_derived_name from ...utils.misc import map_list +from ...utils.widgets import adjust_widget_transform_mesh from ..widgets import create_hand_widget +from ...utils.widgets_basic import create_circle_widget from ...base_rig import stage @@ -42,6 +45,8 @@ class Rig(BaseLimbRig): super().initialize() + self.make_wrist_pivot = self.params.make_ik_wrist_pivot + def prepare_bones(self): orgs = self.bones.org.main @@ -62,16 +67,76 @@ class Rig(BaseLimbRig): def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): create_hand_widget(self.obj, ctrl) #################################################### + # Palm Pivot + + def get_ik_input_bone(self): + if self.make_wrist_pivot: + return self.bones.mch.ik_wrist + else: + return self.get_ik_control_output() + + def get_extra_ik_controls(self): + controls = super().get_extra_ik_controls() + if self.make_wrist_pivot: + controls += [self.bones.ctrl.ik_wrist] + return controls + + @stage.generate_bones + def make_wrist_pivot_control(self): + if self.make_wrist_pivot: + org = self.bones.org.main[2] + self.bones.ctrl.ik_wrist = self.make_wrist_pivot_bone(org) + self.bones.mch.ik_wrist = self.copy_bone(org, make_derived_name(org, 'mch', '_ik_wrist'), scale=0.25) + + def make_wrist_pivot_bone(self, org): + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_ik_wrist'), scale=0.5) + put_bone(self.obj, name, self.get_bone(org).tail) + return name + + @stage.parent_bones + def parent_wrist_pivot_control(self): + if self.make_wrist_pivot: + ctrl = self.bones.ctrl.ik_wrist + self.set_bone_parent(ctrl, self.get_ik_control_output()) + self.set_bone_parent(self.bones.mch.ik_wrist, ctrl) + + @stage.generate_widgets + def make_wrist_pivot_widget(self): + if self.make_wrist_pivot: + ctrl = self.bones.ctrl.ik_wrist + + if self.main_axis == 'x': + obj = create_circle_widget(self.obj, ctrl, head_tail=-0.3, head_tail_x=0.5) + else: + obj = create_circle_widget(self.obj, ctrl, head_tail=0.5, head_tail_x=-0.3) + + if obj: + org_bone = self.get_bone(self.bones.org.main[2]) + offset = org_bone.head - self.get_bone(ctrl).head + adjust_widget_transform_mesh(obj, Matrix.Translation(offset)) + + #################################################### # Settings @classmethod + def add_parameters(self, params): + super().add_parameters(params) + + params.make_ik_wrist_pivot = bpy.props.BoolProperty( + name="IK Wrist Pivot", default=False, + description="Make an extra IK hand control pivoting around the tip of the hand" + ) + + @classmethod def parameters_ui(self, layout, params): + layout.prop(params, "make_ik_wrist_pivot") + super().parameters_ui(layout, params, 'Hand') diff --git a/rigify/rigs/limbs/leg.py b/rigify/rigs/limbs/leg.py index dbdd20cb..b409d009 100644 --- a/rigify/rigs/limbs/leg.py +++ b/rigify/rigs/limbs/leg.py @@ -22,13 +22,14 @@ import bpy import math from itertools import count -from mathutils import Vector +from mathutils import Vector, Matrix from ...utils.rig import is_rig_base_bone from ...utils.bones import align_chain_x_axis, align_bone_x_axis, align_bone_y_axis, align_bone_z_axis from ...utils.bones import align_bone_to_axis, flip_bone, put_bone, align_bone_orientation from ...utils.naming import make_derived_name -from ...utils.misc import map_list +from ...utils.misc import map_list, matrix_from_axis_roll, matrix_from_axis_pair +from ...utils.widgets import adjust_widget_transform_mesh from ..widgets import create_foot_widget, create_ballsocket_widget @@ -62,10 +63,18 @@ class Rig(BaseLimbRig): super().initialize() + self.pivot_type = self.params.foot_pivot_type + self.heel_euler_order = 'ZXY' if self.main_axis == 'x' else 'XZY' + + assert self.pivot_type in {'ANKLE', 'TOE', 'ANKLE_TOE'} + def prepare_bones(self): orgs = self.bones.org.main + foot = self.get_bone(orgs[2]) - foot_x = self.vector_without_z(self.get_bone(orgs[2]).y_axis).cross((0, 0, -1)) + ik_y_axis = (0, 1, 0) + foot_y_axis = -self.vector_without_z(foot.y_axis) + foot_x = foot_y_axis.cross((0, 0, 1)) if self.params.rotation_axis == 'automatic': align_chain_x_axis(self.obj, orgs[0:2]) @@ -84,6 +93,12 @@ class Rig(BaseLimbRig): align_bone_z_axis(self.obj, orgs[2], foot_x) align_bone_z_axis(self.obj, orgs[3], -foot_x) + else: + ik_y_axis = foot_y_axis + + # Orientation of the IK main and roll control bones + self.ik_matrix = matrix_from_axis_roll(ik_y_axis, 0) + self.roll_matrix = matrix_from_axis_pair(ik_y_axis, foot_x, self.main_axis) #################################################### # EXTRA BONES @@ -92,6 +107,8 @@ class Rig(BaseLimbRig): # heel: # Heel location marker bone # ctrl: + # ik_spin: + # Toe spin control. # heel: # Foot roll control # mch: @@ -104,31 +121,71 @@ class Rig(BaseLimbRig): # IK controls def get_extra_ik_controls(self): - return [self.bones.ctrl.heel] + controls = super().get_extra_ik_controls() + [self.bones.ctrl.heel] + if self.pivot_type == 'ANKLE_TOE': + controls += [self.bones.ctrl.ik_spin] + return controls def make_ik_control_bone(self, orgs): name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik')) - - if self.params.rotation_axis == 'automatic' or self.params.auto_align_extremity: - align_bone_to_axis(self.obj, name, 'y', flip=True) - + if self.pivot_type == 'TOE': + put_bone(self.obj, name, self.get_bone(name).tail, matrix=self.ik_matrix) else: - flip_bone(self.obj, name) - - bone = self.get_bone(name) - bone.tail[2] = bone.head[2] - bone.roll = 0 - + put_bone(self.obj, name, None, matrix=self.ik_matrix) return name + def build_ik_pivot(self, ik_name, **args): + heel_bone = self.get_bone(self.bones.org.heel) + args = { + 'position': (heel_bone.head + heel_bone.tail)/2, + **args + } + return super().build_ik_pivot(ik_name, **args) + def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[2], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): - create_foot_widget(self.obj, ctrl) + obj = create_foot_widget(self.obj, ctrl) + if self.pivot_type != 'TOE': + ctrl = self.get_bone(ctrl) + org = self.get_bone(self.bones.org.main[2]) + offset = org.tail - (ctrl.custom_shape_transform or ctrl).head + adjust_widget_transform_mesh(obj, Matrix.Translation(offset)) + + #################################################### + # IK pivot controls + + def get_ik_pivot_output(self): + if self.pivot_type == 'ANKLE_TOE': + return self.bones.ctrl.ik_spin + else: + return self.get_ik_control_output() + + @stage.generate_bones + def make_ik_pivot_controls(self): + if self.pivot_type == 'ANKLE_TOE': + self.bones.ctrl.ik_spin = self.make_ik_spin_bone(self.bones.org.main) + + def make_ik_spin_bone(self, orgs): + name = self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_spin_ik')) + put_bone(self.obj, name, self.get_bone(orgs[3]).head, matrix=self.ik_matrix, scale=0.5) + return name + + @stage.parent_bones + def parent_ik_pivot_controls(self): + if self.pivot_type == 'ANKLE_TOE': + self.set_bone_parent(self.bones.ctrl.ik_spin, self.get_ik_control_output()) + + @stage.generate_widgets + def make_ik_spin_control_widget(self): + if self.pivot_type == 'ANKLE_TOE': + obj = create_ballsocket_widget(self.obj, self.bones.ctrl.ik_spin, size=0.75) + rotfix = Matrix.Rotation(math.pi/2, 4, self.main_axis.upper()) + adjust_widget_transform_mesh(obj, rotfix, local=True) #################################################### # Heel control @@ -136,25 +193,19 @@ class Rig(BaseLimbRig): @stage.generate_bones def make_heel_control_bone(self): org = self.bones.org.main[2] - name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik'), scale=1/2) + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_heel_ik')) + put_bone(self.obj, name, None, matrix=self.roll_matrix, scale=0.5) self.bones.ctrl.heel = name - self.align_roll_bone(org, name, -self.vector_without_z(self.get_bone(org).vector)) - @stage.parent_bones def parent_heel_control_bone(self): - self.set_bone_parent(self.bones.ctrl.heel, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_pivot_output(), inherit_scale='AVERAGE') @stage.configure_bones def configure_heel_control_bone(self): bone = self.get_bone(self.bones.ctrl.heel) bone.lock_location = True, True, True - - if self.main_axis == 'x': - bone.lock_rotation = False, False, True - else: - bone.lock_rotation = True, False, False - + bone.rotation_mode = self.heel_euler_order bone.lock_scale = True, True, True @stage.generate_widgets @@ -173,34 +224,27 @@ class Rig(BaseLimbRig): def make_roll_mch_bones(self, foot, toe, heel): foot_bone = self.get_bone(foot) heel_bone = self.get_bone(heel) - axis = -self.vector_without_z(foot_bone.vector) - - roll1 = self.copy_bone(foot, make_derived_name(heel, 'mch', '_roll1')) - flip_bone(self.obj, roll1) - self.align_roll_bone(foot, roll1, -foot_bone.vector) + heel_middle = (heel_bone.head + heel_bone.tail) / 2 - roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=1/4) - - put_bone(self.obj, roll2, (heel_bone.head + heel_bone.tail) / 2) - self.align_roll_bone(foot, roll2, -axis) + result = self.copy_bone(foot, make_derived_name(foot, 'mch', '_roll'), scale=0.25) + roll1 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll1'), scale=0.3) + roll2 = self.copy_bone(toe, make_derived_name(heel, 'mch', '_roll2'), scale=0.3) rock1 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock1')) - - align_bone_to_axis(self.obj, rock1, 'y', roll=0, length=heel_bone.length/2, flip=True) - align_bone_y_axis(self.obj, rock1, axis) - rock2 = self.copy_bone(heel, make_derived_name(heel, 'mch', '_rock2')) - align_bone_to_axis(self.obj, rock2, 'y', roll=0, length=heel_bone.length/2) - align_bone_y_axis(self.obj, rock2, axis) + put_bone(self.obj, roll1, None, matrix=self.roll_matrix) + put_bone(self.obj, roll2, heel_middle, matrix=self.roll_matrix) + put_bone(self.obj, rock1, heel_bone.tail, matrix=self.roll_matrix, scale=0.5) + put_bone(self.obj, rock2, heel_bone.head, matrix=self.roll_matrix, scale=0.5) - return [ rock2, rock1, roll2, roll1 ] + return [ rock2, rock1, roll2, roll1, result ] @stage.parent_bones def parent_roll_mch_chain(self): chain = self.bones.mch.heel - self.set_bone_parent(chain[0], self.bones.ctrl.ik) + self.set_bone_parent(chain[0], self.get_ik_pivot_output()) self.parent_bone_chain(chain) @stage.rig_bones @@ -208,28 +252,37 @@ class Rig(BaseLimbRig): self.rig_roll_mch_bones(self.bones.mch.heel, self.bones.ctrl.heel, self.bones.org.heel) def rig_roll_mch_bones(self, chain, heel, org_heel): - rock2, rock1, roll2, roll1 = chain + rock2, rock1, roll2, roll1, result = chain + + # This order is required for correct working of the constraints + for bone in chain: + self.get_bone(bone).rotation_mode = self.heel_euler_order - self.make_constraint(roll1, 'COPY_ROTATION', heel, space='LOCAL') - self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL') + self.make_constraint(roll1, 'COPY_ROTATION', heel, space='POSE') if self.main_axis == 'x': - self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_x=True, max_x=DEG_360, space='LOCAL') - self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_x=-DEG_360, space='LOCAL') + self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(True, False, False)) + self.make_constraint(roll2, 'LIMIT_ROTATION', min_x=-DEG_360, space='LOCAL') else: - self.make_constraint(roll1, 'LIMIT_ROTATION', use_limit_z=True, max_z=DEG_360, space='LOCAL') - self.make_constraint(roll2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_z=-DEG_360, space='LOCAL') + self.make_constraint(roll2, 'COPY_ROTATION', heel, space='LOCAL', use_xyz=(False, False, True)) + self.make_constraint(roll2, 'LIMIT_ROTATION', min_z=-DEG_360, space='LOCAL') direction = self.get_main_axis(self.get_bone(heel)).dot(self.get_bone(org_heel).vector) if direction < 0: rock2, rock1 = rock1, rock2 - self.make_constraint(rock1, 'COPY_ROTATION', heel, space='LOCAL') - self.make_constraint(rock2, 'COPY_ROTATION', heel, space='LOCAL') + self.make_constraint( + rock1, 'COPY_ROTATION', heel, space='LOCAL', + use_xyz=(False, True, False), + ) + self.make_constraint( + rock2, 'COPY_ROTATION', heel, space='LOCAL', + use_xyz=(False, True, False), + ) - self.make_constraint(rock1, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, max_y=DEG_360, space='LOCAL') - self.make_constraint(rock2, 'LIMIT_ROTATION', use_limit_xyz=ALL_TRUE, min_y=-DEG_360, space='LOCAL') + self.make_constraint(rock1, 'LIMIT_ROTATION', max_y=DEG_360, space='LOCAL') + self.make_constraint(rock2, 'LIMIT_ROTATION', min_y=-DEG_360, space='LOCAL') #################################################### @@ -237,7 +290,7 @@ class Rig(BaseLimbRig): def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org): if i == 3: - align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[-2]) + align_bone_orientation(self.obj, parent_mch, self.bones.mch.heel[2]) self.set_bone_parent(parent_mch, prev_org, use_connect=True) @@ -246,7 +299,7 @@ class Rig(BaseLimbRig): def rig_fk_parent_bone(self, i, parent_mch, org): if i == 3: - con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[-2]) + con = self.make_constraint(parent_mch, 'COPY_TRANSFORMS', self.bones.mch.heel[2]) self.make_driver(con, 'influence', variables=[(self.prop_bone, 'IK_FK')], polynomial=[1.0, -1.0]) @@ -257,8 +310,6 @@ class Rig(BaseLimbRig): #################################################### # IK system MCH - ik_input_head_tail = 1.0 - def get_ik_input_bone(self): return self.bones.mch.heel[-1] @@ -266,14 +317,35 @@ class Rig(BaseLimbRig): def parent_ik_mch_chain(self): super().parent_ik_mch_chain() - self.set_bone_parent(self.bones.mch.ik_target, self.bones.ctrl.heel) + self.set_bone_parent(self.bones.mch.ik_target, self.bones.mch.heel[-1]) #################################################### # Settings @classmethod + def add_parameters(self, params): + super().add_parameters(params) + + items = [ + ('ANKLE', 'Ankle', + 'The foots pivots at the ankle'), + ('TOE', 'Toe', + 'The foot pivots around the base of the toe'), + ('ANKLE_TOE', 'Ankle and Toe', + 'The foots pivots at the ankle, with extra toe pivot'), + ] + + params.foot_pivot_type = bpy.props.EnumProperty( + items = items, + name = "Foot Pivot", + default = 'ANKLE_TOE' + ) + + @classmethod def parameters_ui(self, layout, params): + layout.prop(params, 'foot_pivot_type') + super().parameters_ui(layout, params, 'Foot') diff --git a/rigify/rigs/limbs/limb_rigs.py b/rigify/rigs/limbs/limb_rigs.py index 81079c05..f1eb8639 100644 --- a/rigify/rigs/limbs/limb_rigs.py +++ b/rigify/rigs/limbs/limb_rigs.py @@ -23,12 +23,12 @@ import json from ...utils.animation import add_generic_snap_fk_to_ik, add_fk_ik_snap_buttons from ...utils.rig import connected_children_names -from ...utils.bones import BoneDict, put_bone, compute_chain_x_axis, align_bone_orientation -from ...utils.bones import align_bone_x_axis, align_bone_y_axis, align_bone_z_axis +from ...utils.bones import BoneDict, put_bone, compute_chain_x_axis, align_bone_orientation, set_bone_widget_transform from ...utils.naming import strip_org, make_derived_name from ...utils.layers import ControlLayersOption from ...utils.misc import pairwise_nozip, padnone, map_list from ...utils.switch_parent import SwitchParentBuilder +from ...utils.components import CustomPivotControl from ...base_rig import stage, BaseRig @@ -64,6 +64,7 @@ class BaseLimbRig(BaseRig): self.segments = self.params.segments self.bbone_segments = self.params.bbones + self.use_ik_pivot = self.params.make_custom_pivot rot_axis = self.params.rotation_axis @@ -124,15 +125,6 @@ class BaseLimbRig(BaseRig): bone = self.get_bone(org) return bone.head + bone.vector * (seg / self.segments) - def align_roll_bone(self, org, name, y_axis): - if y_axis: - align_bone_y_axis(self.obj, name, y_axis) - - if self.main_axis == 'x': - align_bone_x_axis(self.obj, name, self.get_bone(org).x_axis) - else: - align_bone_z_axis(self.obj, name, self.get_bone(org).z_axis) - @staticmethod def vector_without_z(vector): return Vector((vector[0], vector[1], 0)) @@ -154,6 +146,8 @@ class BaseLimbRig(BaseRig): # IK controls # ik_vispole # IK pole visualization. + # ik_pivot + # Custom IK pivot (optional). # mch: # master: # Parent of the master control. @@ -161,6 +155,8 @@ class BaseLimbRig(BaseRig): # FK follow behavior. # fk[]: # FK chain parents (or None) + # ik_pivot + # Custom IK pivot result (optional). # ik_stretch # IK stretch switch implementation. # ik_target @@ -328,22 +324,30 @@ class BaseLimbRig(BaseRig): # IK controls def get_extra_ik_controls(self): - return [] + if self.component_ik_pivot: + return [self.component_ik_pivot.control] + else: + return [] def get_all_ik_controls(self): ctrl = self.bones.ctrl - return [ctrl.ik_base, ctrl.ik_pole, ctrl.ik] + self.get_extra_ik_controls() + controls = [ctrl.ik_base, ctrl.ik_pole, ctrl.ik] + return controls + self.get_extra_ik_controls() @stage.generate_bones def make_ik_controls(self): orgs = self.bones.org.main - self.bones.ctrl.ik_base = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik')) + self.bones.ctrl.ik_base = self.make_ik_base_bone(orgs) self.bones.ctrl.ik_pole = self.make_ik_pole_bone(orgs) - self.bones.ctrl.ik = self.make_ik_control_bone(orgs) + self.bones.ctrl.ik = ik_name = self.make_ik_control_bone(orgs) + self.component_ik_pivot = self.build_ik_pivot(ik_name) self.build_ik_parent_switch(SwitchParentBuilder(self.generator)) + def make_ik_base_bone(self, orgs): + return self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik')) + def make_ik_pole_bone(self, orgs): name = self.copy_bone(orgs[0], make_derived_name(orgs[0], 'ctrl', '_ik_target')) @@ -357,10 +361,25 @@ class BaseLimbRig(BaseRig): def make_ik_control_bone(self, orgs): return self.copy_bone(orgs[2], make_derived_name(orgs[2], 'ctrl', '_ik')) + def build_ik_pivot(self, ik_name, **args): + if self.use_ik_pivot: + return CustomPivotControl(self, 'ik_pivot', ik_name, parent=ik_name, **args) + + def get_ik_control_output(self): + if self.component_ik_pivot: + return self.component_ik_pivot.output + else: + return self.bones.ctrl.ik + def register_switch_parents(self, pbuilder): if self.rig_parent_bone: pbuilder.register_parent(self, self.rig_parent_bone) + pbuilder.register_parent( + self, self.get_ik_control_output, name=self.bones.ctrl.ik, + exclude_self=True, tags={'limb_ik', 'child'}, + ) + def build_ik_parent_switch(self, pbuilder): ctrl = self.bones.ctrl @@ -398,9 +417,13 @@ class BaseLimbRig(BaseRig): @stage.generate_widgets def make_ik_control_widgets(self): - self.make_ik_base_widget(self.bones.ctrl.ik_base) - self.make_ik_pole_widget(self.bones.ctrl.ik_pole) - self.make_ik_ctrl_widget(self.bones.ctrl.ik) + ctrl = self.bones.ctrl + + set_bone_widget_transform(self.obj, ctrl.ik, self.get_ik_control_output()) + + self.make_ik_base_widget(ctrl.ik_base) + self.make_ik_pole_widget(ctrl.ik_pole) + self.make_ik_ctrl_widget(ctrl.ik) def make_ik_base_widget(self, ctrl): if self.main_axis == 'x': @@ -453,7 +476,7 @@ class BaseLimbRig(BaseRig): ik_input_head_tail = 0.0 def get_ik_input_bone(self): - return self.bones.ctrl.ik + return self.get_ik_control_output() def get_ik_output_chain(self): return [self.bones.ctrl.ik_base, self.bones.mch.ik_end, self.bones.mch.ik_target] @@ -670,7 +693,13 @@ class BaseLimbRig(BaseRig): @stage.parent_bones def parent_tweak_mch_chain(self): - for mch, entry in zip(self.bones.mch.tweak, self.segment_table_tweak): + for args in zip(count(0), self.bones.mch.tweak, self.segment_table_tweak): + self.parent_tweak_mch_bone(*args) + + def parent_tweak_mch_bone(self, i, mch, entry): + if i == 0: + self.set_bone_parent(mch, self.rig_parent_bone, inherit_scale='FIX_SHEAR') + else: self.set_bone_parent(mch, entry.org) @stage.rig_bones @@ -694,6 +723,10 @@ class BaseLimbRig(BaseRig): elif entry.seg_idx is not None: self.make_constraint(tweak, 'COPY_SCALE', 'root', use_make_uniform=True) + if i == 0: + self.make_constraint(tweak, 'COPY_LOCATION', entry.org) + self.make_constraint(tweak, 'DAMPED_TRACK', entry.org, head_tail=1) + #################################################### # Deform chain @@ -786,17 +819,23 @@ class BaseLimbRig(BaseRig): ) params.segments = bpy.props.IntProperty( - name = 'limb segments', + name = 'Limb Segments', default = 2, min = 1, - description = 'Number of segments' + description = 'Number of limb segments' ) params.bbones = bpy.props.IntProperty( - name = 'bbone segments', + name = 'B-Bone Segments', default = 10, min = 1, - description = 'Number of segments' + description = 'Number of B-Bone segments' + ) + + params.make_custom_pivot = bpy.props.BoolProperty( + name = "Custom Pivot Control", + default = False, + description = "Create a rotation pivot control that can be repositioned arbitrarily" ) # Setting up extra layers for the FK and tweak @@ -820,6 +859,8 @@ class BaseLimbRig(BaseRig): r = layout.row() r.prop(params, "bbones") + layout.prop(params, 'make_custom_pivot', text="Custom IK Pivot") + ControlLayersOption.FK.parameters_ui(layout, params) ControlLayersOption.TWEAK.parameters_ui(layout, params) @@ -843,8 +884,6 @@ class RigifyLimbIk2FkBase: ctrl_bones: StringProperty(name="IK Controls") extra_ctrls: StringProperty(name="Extra IK Controls") - keyflags = None - def init_execute(self, context): if self.fk_bones: self.fk_bone_list = json.loads(self.fk_bones) @@ -921,13 +960,11 @@ class RigifyLimbIk2FkBase: class POSE_OT_rigify_limb_ik2fk(RigifyLimbIk2FkBase, RigifySingleUpdateMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_ik2fk_" + rig_id bl_label = "Snap IK->FK" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Snap the IK chain to FK result" class POSE_OT_rigify_limb_ik2fk_bake(RigifyLimbIk2FkBase, RigifyBakeKeyframesMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_ik2fk_bake_" + rig_id bl_label = "Apply Snap IK->FK To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Snap the IK chain keyframes to FK result" def execute_scan_curves(self, context, obj): @@ -968,8 +1005,6 @@ SCRIPT_UTILITIES_OP_TOGGLE_POLE = SCRIPT_UTILITIES_OP_SNAP_IK_FK + [''' class RigifyLimbTogglePoleBase(RigifyLimbIk2FkBase): use_pole: bpy.props.BoolProperty(name="Use Pole Vector") - keyflags_switch = None - def save_frame_state(self, context, obj): return get_chain_transform_matrices(obj, self.ik_bone_list) @@ -1007,13 +1042,11 @@ class RigifyLimbTogglePoleBase(RigifyLimbIk2FkBase): class POSE_OT_rigify_limb_toggle_pole(RigifyLimbTogglePoleBase, RigifySingleUpdateMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_toggle_pole_" + rig_id bl_label = "Toggle Pole" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Switch the IK chain between pole and rotation" class POSE_OT_rigify_limb_toggle_pole_bake(RigifyLimbTogglePoleBase, RigifyBakeKeyframesMixin, bpy.types.Operator): bl_idname = "pose.rigify_limb_toggle_pole_bake_" + rig_id bl_label = "Apply Toggle Pole To Keyframes" - bl_options = {'UNDO', 'INTERNAL'} bl_description = "Switch the IK chain between pole and rotation over a frame range" def execute_scan_curves(self, context, obj): diff --git a/rigify/rigs/limbs/paw.py b/rigify/rigs/limbs/paw.py index f8cb1f9f..28374eec 100644 --- a/rigify/rigs/limbs/paw.py +++ b/rigify/rigs/limbs/paw.py @@ -84,7 +84,7 @@ class Rig(BaseLimbRig): # IK controls def get_extra_ik_controls(self): - return [self.bones.ctrl.heel] + return super().get_extra_ik_controls() + [self.bones.ctrl.heel] def make_ik_control_bone(self, orgs): name = self.copy_bone(orgs[3], make_derived_name(orgs[2], 'ctrl', '_ik')) @@ -107,7 +107,7 @@ class Rig(BaseLimbRig): def register_switch_parents(self, pbuilder): super().register_switch_parents(pbuilder) - pbuilder.register_parent(self, self.bones.org.main[3], exclude_self=True) + pbuilder.register_parent(self, self.bones.org.main[3], exclude_self=True, tags={'limb_end'}) def make_ik_ctrl_widget(self, ctrl): create_foot_widget(self.obj, ctrl) @@ -126,7 +126,7 @@ class Rig(BaseLimbRig): @stage.parent_bones def parent_heel_control_bone(self): - self.set_bone_parent(self.bones.ctrl.heel, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.ctrl.heel, self.get_ik_control_output()) @stage.configure_bones def configure_heel_control_bone(self): @@ -150,7 +150,7 @@ class Rig(BaseLimbRig): def parent_fk_parent_bone(self, i, parent_mch, prev_ctrl, org, prev_org): if i == 3: self.set_bone_parent(parent_mch, prev_org, use_connect=True) - self.set_bone_parent(self.bones.mch.toe_socket, self.bones.ctrl.ik) + self.set_bone_parent(self.bones.mch.toe_socket, self.get_ik_control_output()) else: super().parent_fk_parent_bone(i, parent_mch, prev_ctrl, org, prev_org) diff --git a/rigify/rigs/limbs/simple_tentacle.py b/rigify/rigs/limbs/simple_tentacle.py index 10bdd2b5..25d26e86 100644 --- a/rigify/rigs/limbs/simple_tentacle.py +++ b/rigify/rigs/limbs/simple_tentacle.py @@ -78,7 +78,7 @@ class Rig(TweakChainRig): ) # Widgets - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) diff --git a/rigify/rigs/limbs/super_finger.py b/rigify/rigs/limbs/super_finger.py index 1a9171a7..0b3fcd8a 100644 --- a/rigify/rigs/limbs/super_finger.py +++ b/rigify/rigs/limbs/super_finger.py @@ -24,11 +24,12 @@ import re from itertools import count from ...utils.errors import MetarigError -from ...utils.bones import flip_bone, align_chain_x_axis +from ...utils.bones import put_bone, flip_bone, align_chain_x_axis, set_bone_widget_transform from ...utils.naming import make_derived_name from ...utils.widgets import create_widget from ...utils.widgets_basic import create_circle_widget from ...utils.misc import map_list +from ...utils.layers import ControlLayersOption from ...base_rig import stage @@ -40,7 +41,7 @@ class Rig(SimpleChainRig): def initialize(self): super().initialize() - self.bbone_segments = 8 + self.bbone_segments = self.params.bbones self.first_parent = self.get_bone_parent(self.bones.org[0]) def prepare_bones(self): @@ -116,6 +117,8 @@ class Rig(SimpleChainRig): for args in zip(count(0), self.bones.ctrl.fk, self.bones.org + [None]): self.configure_control_bone(*args) + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.fk) + def configure_control_bone(self, i, ctrl, org): if org: self.copy_bone_properties(org, ctrl) @@ -125,11 +128,13 @@ class Rig(SimpleChainRig): bone.lock_rotation = (True, True, True) bone.lock_scale = (True, True, True) - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): if ctrl == self.bones.ctrl.fk[-1]: # Tip control create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.0) else: + set_bone_widget_transform(self.obj, ctrl, self.bones.org[i]) + create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5) ############################## @@ -234,11 +239,12 @@ class Rig(SimpleChainRig): def configure_master_properties(self): master = self.bones.ctrl.master - self.make_property(master, 'finger_curve', 0.0, description="Rubber hose finger cartoon effect") + if self.bbone_segments > 1: + self.make_property(master, 'finger_curve', 0.0, description="Rubber hose finger cartoon effect") - # Create UI - panel = self.script.panel_with_selected_check(self, self.bones.ctrl.flatten()) - panel.custom_prop(master, 'finger_curve', text="Curvature", slider=True) + # Create UI + panel = self.script.panel_with_selected_check(self, self.bones.ctrl.flatten()) + panel.custom_prop(master, 'finger_curve', text="Curvature", slider=True) def rig_deform_bone(self, i, deform, org): master = self.bones.ctrl.master @@ -246,8 +252,9 @@ class Rig(SimpleChainRig): self.make_constraint(deform, 'COPY_TRANSFORMS', org) - self.make_driver(bone.bone, 'bbone_easein', variables=[(master, 'finger_curve')]) - self.make_driver(bone.bone, 'bbone_easeout', variables=[(master, 'finger_curve')]) + if self.bbone_segments > 1: + self.make_driver(bone.bone, 'bbone_easein', variables=[(master, 'finger_curve')]) + self.make_driver(bone.bone, 'bbone_easeout', variables=[(master, 'finger_curve')]) ############### # OPTIONS @@ -261,6 +268,15 @@ class Rig(SimpleChainRig): ('-X', '-X manual', ''), ('-Y', '-Y manual', ''), ('-Z', '-Z manual', '')] params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='automatic') + params.bbones = bpy.props.IntProperty( + name = 'B-Bone Segments', + default = 10, + min = 1, + description = 'Number of B-Bone segments' + ) + + ControlLayersOption.TWEAK.add_parameters(params) + @classmethod def parameters_ui(self, layout, params): """ Create the ui for the rig parameters. @@ -269,6 +285,10 @@ class Rig(SimpleChainRig): r.label(text="Bend rotation axis:") r.prop(params, "primary_rotation_axis", text="") + layout.prop(params, 'bbones') + + ControlLayersOption.TWEAK.parameters_ui(layout, params) + def create_sample(obj): # generated by rigify.utils.write_metarig @@ -321,10 +341,6 @@ def create_sample(obj): pbone.lock_scale = (False, False, False) pbone.rotation_mode = 'QUATERNION' try: - pbone.rigify_parameters.separate_extra_layers = True - except AttributeError: - pass - try: pbone.rigify_parameters.extra_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] except AttributeError: pass diff --git a/rigify/rigs/limbs/super_palm.py b/rigify/rigs/limbs/super_palm.py index 8bcbabf8..9c03b2fe 100644 --- a/rigify/rigs/limbs/super_palm.py +++ b/rigify/rigs/limbs/super_palm.py @@ -24,6 +24,7 @@ import re from math import cos, pi from itertools import count, repeat +from rigify.utils.rig import is_rig_base_bone from rigify.utils.naming import strip_org, make_derived_name from rigify.utils.widgets import create_widget from rigify.utils.misc import map_list @@ -43,7 +44,7 @@ def bone_siblings(obj, bone): bones = [] for b in parent.children: - if b.name != bone: + if b.name != bone and not is_rig_base_bone(obj, b.name): bones += [b.name] return bones diff --git a/rigify/rigs/spines/basic_spine.py b/rigify/rigs/spines/basic_spine.py index 269889cf..c2905463 100644 --- a/rigify/rigs/spines/basic_spine.py +++ b/rigify/rigs/spines/basic_spine.py @@ -19,13 +19,16 @@ # <pep8 compliant> import bpy +import math from itertools import count, repeat +from mathutils import Matrix from ...utils.errors import MetarigError from ...utils.layers import ControlLayersOption -from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name -from ...utils.bones import BoneDict, put_bone, align_bone_to_axis +from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name, make_derived_name +from ...utils.bones import BoneDict, put_bone, align_bone_to_axis, align_bone_orientation, set_bone_widget_transform +from ...utils.widgets import adjust_widget_transform_mesh from ...utils.widgets_basic import create_circle_widget from ...utils.misc import map_list @@ -43,6 +46,7 @@ class Rig(BaseSpineRig): # Check if user provided the pivot position self.pivot_pos = self.params.pivot_pos + self.use_fk = self.params.make_fk_controls if not (0 < self.pivot_pos < len(self.bones.org)): self.raise_error("Please specify a valid pivot bone position.") @@ -55,6 +59,9 @@ class Rig(BaseSpineRig): # ctrl: # master, hips, chest: # Main controls. + # fk: + # chest[], hips[]: + # FK controls. # tweak[]: # Tweak control chain. # mch: @@ -73,19 +80,15 @@ class Rig(BaseSpineRig): #################################################### # Master control bone - @stage.generate_bones - def make_master_control(self): - super().make_master_control() - - # Put the main control in the middle of the hip bone - base_bone = self.get_bone(self.bones.org[0]) - put_bone(self.obj, self.bones.ctrl.master, (base_bone.head + base_bone.tail) / 2) + def get_master_control_pos(self, orgs): + base_bone = self.get_bone(orgs[0]) + return (base_bone.head + base_bone.tail) / 2 #################################################### # Main control bones @stage.generate_bones - def make_control_chain(self): + def make_end_control_bones(self): orgs = self.bones.org pivot = self.pivot_pos @@ -103,33 +106,90 @@ class Rig(BaseSpineRig): return name @stage.parent_bones - def parent_control_chain(self): + def parent_end_control_bones(self): ctrl = self.bones.ctrl - self.set_bone_parent(ctrl.hips, ctrl.master) - self.set_bone_parent(ctrl.chest, ctrl.master) - - @stage.configure_bones - def configure_control_chain(self): - pass + pivot = self.get_master_control_output() + self.set_bone_parent(ctrl.hips, pivot) + self.set_bone_parent(ctrl.chest, pivot) @stage.generate_widgets - def make_control_widgets(self): + def make_end_control_widgets(self): ctrl = self.bones.ctrl mch = self.bones.mch - self.make_control_widget(ctrl.hips, mch.wgt_hips) - self.make_control_widget(ctrl.chest, mch.wgt_chest) + self.make_end_control_widget(ctrl.hips, mch.wgt_hips) + self.make_end_control_widget(ctrl.chest, mch.wgt_chest) + + def make_end_control_widget(self, ctrl, wgt_mch): + shape_bone = self.get_bone(wgt_mch) + is_horizontal = abs(shape_bone.z_axis.normalized().y) < 0.7 - def make_control_widget(self, ctrl, wgt_mch): - self.get_bone(ctrl).custom_shape_transform = self.get_bone(wgt_mch) + set_bone_widget_transform(self.obj, ctrl, wgt_mch) - create_circle_widget( + obj = create_circle_widget( self.obj, ctrl, - radius=1.0, + radius=1.2 if is_horizontal else 1.1, head_tail=0.0, head_tail_x=1.0, with_line=False, ) + if is_horizontal: + # Tilt the widget toward the ground for horizontal (animal) spines + angle = math.copysign(28, shape_bone.x_axis.x) + rotmat = Matrix.Rotation(math.radians(angle), 4, 'X') + adjust_widget_transform_mesh(obj, rotmat, local=True) + + #################################################### + # FK control bones + + @stage.generate_bones + def make_control_chain(self): + if self.use_fk: + orgs = self.bones.org + self.bones.ctrl.fk = self.fk_result = BoneDict( + hips = map_list(self.make_control_bone, count(0), orgs[0:self.pivot_pos], repeat(True)), + chest = map_list(self.make_control_bone, count(self.pivot_pos), orgs[self.pivot_pos:], repeat(False)), + ) + + def make_control_bone(self, i, org, is_hip): + name = self.copy_bone(org, make_derived_name(org, 'ctrl', '_fk'), parent=False) + if is_hip: + put_bone(self.obj, name, self.get_bone(name).tail) + return name + + @stage.parent_bones + def parent_control_chain(self): + if self.use_fk: + chain = self.bones.mch.chain + fk = self.bones.ctrl.fk + for child, parent in zip(fk.hips, chain.hips): + self.set_bone_parent(child, parent) + for child, parent in zip(fk.chest, chain.chest): + self.set_bone_parent(child, parent) + + @stage.configure_bones + def configure_control_chain(self): + if self.use_fk: + fk = self.bones.ctrl.fk + for args in zip(count(0), fk.hips + fk.chest, self.bones.org): + self.configure_control_bone(*args) + + ControlLayersOption.FK.assign_rig(self, fk.hips + fk.chest) + + @stage.generate_widgets + def make_control_widgets(self): + if self.use_fk: + fk = self.bones.ctrl.fk + for ctrl in fk.hips: + self.make_control_widget(ctrl, True) + for ctrl in fk.chest: + self.make_control_widget(ctrl, False) + + def make_control_widget(self, ctrl, is_hip): + obj = create_circle_widget(self.obj, ctrl, radius=1.0, head_tail=0.5) + if is_hip: + adjust_widget_transform_mesh(obj, Matrix.Diagonal((1, -1, 1, 1)), local=True) + #################################################### # MCH bones associated with main controls @@ -153,16 +213,16 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_mch_control_bones(self): mch = self.bones.mch - self.set_bone_parent(mch.pivot, mch.chain.chest[0]) - self.set_bone_parent(mch.wgt_hips, mch.chain.hips[0]) - self.set_bone_parent(mch.wgt_chest, mch.chain.chest[-1]) + fk = self.fk_result + self.set_bone_parent(mch.pivot, fk.chest[0]) + self.set_bone_parent(mch.wgt_hips, fk.hips[0]) + self.set_bone_parent(mch.wgt_chest, fk.chest[-1]) + align_bone_orientation(self.obj, mch.pivot, fk.hips[-1]) @stage.rig_bones def rig_mch_control_bones(self): mch = self.bones.mch - # Is it actually intending to compute average of these, or is this really intentional? - # This effectively adds rotations of the hip and chest controls. - self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', mch.chain.hips[-1], space='LOCAL') + self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', self.fk_result.hips[-1], influence=0.5) #################################################### # MCH chain for distributing hip & chest transform @@ -174,6 +234,8 @@ class Rig(BaseSpineRig): hips = map_list(self.make_mch_bone, orgs[0:self.pivot_pos], repeat(True)), chest = map_list(self.make_mch_bone, orgs[self.pivot_pos:], repeat(False)), ) + if not self.use_fk: + self.fk_result = self.bones.mch.chain def make_mch_bone(self, org, is_hip): name = self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) @@ -182,10 +244,13 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_mch_chain(self): - master = self.bones.ctrl.master + master = self.get_master_control_output() chain = self.bones.mch.chain - self.parent_bone_chain([master, *reversed(chain.hips)]) - self.parent_bone_chain([master, *chain.chest]) + fk = self.fk_result + for child, parent in zip(reversed(chain.hips), [master, *reversed(fk.hips)]): + self.set_bone_parent(child, parent) + for child, parent in zip(chain.chest, [master, *fk.chest]): + self.set_bone_parent(child, parent) @stage.rig_bones def rig_mch_chain(self): @@ -205,7 +270,7 @@ class Rig(BaseSpineRig): @stage.parent_bones def parent_tweak_chain(self): mch = self.bones.mch - chain = mch.chain + chain = self.fk_result parents = [chain.hips[0], *chain.hips[0:-1], mch.pivot, *chain.chest[1:], chain.chest[-1]] for args in zip(self.bones.ctrl.tweak, parents): self.set_bone_parent(*args) @@ -224,6 +289,13 @@ class Rig(BaseSpineRig): super().add_parameters(params) + params.make_fk_controls = bpy.props.BoolProperty( + name="FK Controls", default=True, + description="Generate an FK control chain" + ) + + ControlLayersOption.FK.add_parameters(params) + @classmethod def parameters_ui(self, layout, params): r = layout.row() @@ -231,6 +303,11 @@ class Rig(BaseSpineRig): super().parameters_ui(layout, params) + layout.prop(params, 'make_fk_controls') + + if params.make_fk_controls: + ControlLayersOption.FK.parameters_ui(layout, params) + def create_sample(obj): # generated by rigify.utils.write_metarig diff --git a/rigify/rigs/spines/basic_tail.py b/rigify/rigs/spines/basic_tail.py index 845c3ca3..be054e7d 100644 --- a/rigify/rigs/spines/basic_tail.py +++ b/rigify/rigs/spines/basic_tail.py @@ -23,7 +23,8 @@ import bpy from itertools import count from ...utils.naming import strip_org, make_derived_name -from ...utils.bones import put_bone, flip_bone, is_same_position, connect_bbone_chain_handles, align_bone_orientation +from ...utils.bones import put_bone, flip_bone, is_same_position, connect_bbone_chain_handles +from ...utils.bones import align_bone_orientation, set_bone_widget_transform from ...utils.widgets_basic import create_circle_widget from ...utils.layers import ControlLayersOption from ...utils.misc import map_list @@ -69,7 +70,7 @@ class Rig(BaseHeadTailRig): @stage.generate_widgets def make_master_control_widget(self): bone = self.bones.ctrl.master - self.get_bone(bone).custom_shape_transform = self.get_bone(self.bones.ctrl.tweak[-1]) + set_bone_widget_transform(self.obj, bone, self.bones.ctrl.tweak[-1]) create_ballsocket_widget(self.obj, bone, size=0.7) #################################################### @@ -94,7 +95,7 @@ class Rig(BaseHeadTailRig): ) # Widgets - def make_control_widget(self, ctrl): + def make_control_widget(self, i, ctrl): create_circle_widget(self.obj, ctrl, radius=0.5, head_tail=0.75) #################################################### diff --git a/rigify/rigs/spines/spine_rigs.py b/rigify/rigs/spines/spine_rigs.py index 6628289f..070a6bd3 100644 --- a/rigify/rigs/spines/spine_rigs.py +++ b/rigify/rigs/spines/spine_rigs.py @@ -24,9 +24,10 @@ from itertools import count from ...utils.layers import ControlLayersOption from ...utils.naming import make_derived_name -from ...utils.bones import align_bone_orientation, align_bone_to_axis +from ...utils.bones import align_bone_orientation, align_bone_to_axis, put_bone, set_bone_widget_transform from ...utils.widgets_basic import create_cube_widget from ...utils.switch_parent import SwitchParentBuilder +from ...utils.components import CustomPivotControl from ...base_rig import stage @@ -44,6 +45,7 @@ class BaseSpineRig(TweakChainRig): if len(self.bones.org) < 3: self.raise_error("Input to rig type must be a chain of 3 or more bones.") + self.use_torso_pivot = self.params.make_custom_pivot self.length = sum([self.get_bone(b).length for b in self.bones.org]) #################################################### @@ -52,6 +54,11 @@ class BaseSpineRig(TweakChainRig): # ctrl: # master # Main control. + # master_pivot + # Custom pivot under the master control. + # mch: + # master_pivot + # Final output of the custom pivot. # #################################################### @@ -60,17 +67,43 @@ class BaseSpineRig(TweakChainRig): @stage.generate_bones def make_master_control(self): - self.bones.ctrl.master = name = self.copy_bone(self.bones.org[0], 'torso') + self.bones.ctrl.master = name = self.make_master_control_bone(self.bones.org) + self.component_torso_pivot = self.build_master_pivot(name) + self.build_parent_switch(name) + + def get_master_control_pos(self, orgs): + return self.get_bone(orgs[0]).head + + def make_master_control_bone(self, orgs): + name = self.copy_bone(orgs[0], 'torso') + put_bone(self.obj, name, self.get_master_control_pos(orgs)) align_bone_to_axis(self.obj, name, 'y', length=self.length * 0.6) + return name - self.build_parent_switch(name) + def build_master_pivot(self, master_name, **args): + if self.use_torso_pivot: + return CustomPivotControl( + self, 'master_pivot', master_name, parent=master_name, **args + ) + + def get_master_control_output(self): + if self.component_torso_pivot: + return self.component_torso_pivot.output + else: + return self.bones.ctrl.master def build_parent_switch(self, master_name): pbuilder = SwitchParentBuilder(self.generator) - pbuilder.register_parent(self, master_name, name='Torso') + + org_parent = self.get_bone_parent(self.bones.org[0]) + parents = [org_parent] if org_parent else [] + + pbuilder.register_parent(self, self.get_master_control_output, name='Torso', tags={'torso', 'child'}) + pbuilder.build_child( self, master_name, exclude_self=True, + extra_parents=parents, select_parent=org_parent, prop_id='torso_parent', prop_name='Torso Parent', controls=lambda: self.bones.flatten('ctrl'), ) @@ -78,8 +111,8 @@ class BaseSpineRig(TweakChainRig): self.register_parent_bones(pbuilder) def register_parent_bones(self, pbuilder): - pbuilder.register_parent(self, self.bones.org[0], name='Hips', exclude_self=True) - pbuilder.register_parent(self, self.bones.org[-1], name='Chest', exclude_self=True) + pbuilder.register_parent(self, self.bones.org[0], name='Hips', exclude_self=True, tags={'hips'}) + pbuilder.register_parent(self, self.bones.org[-1], name='Chest', exclude_self=True, tags={'chest'}) @stage.parent_bones def parent_master_control(self): @@ -91,10 +124,9 @@ class BaseSpineRig(TweakChainRig): @stage.generate_widgets def make_master_control_widget(self): - create_cube_widget( - self.obj, self.bones.ctrl.master, - radius=0.5, - ) + master = self.bones.ctrl.master + set_bone_widget_transform(self.obj, master, self.get_master_control_output()) + create_cube_widget(self.obj, master, radius=0.5) #################################################### # Tweak bones @@ -117,11 +149,19 @@ class BaseSpineRig(TweakChainRig): @classmethod def add_parameters(self, params): + params.make_custom_pivot = bpy.props.BoolProperty( + name = "Custom Pivot Control", + default = False, + description = "Create a rotation pivot control that can be repositioned arbitrarily" + ) + # Setting up extra layers for the FK and tweak ControlLayersOption.TWEAK.add_parameters(params) @classmethod def parameters_ui(self, layout, params): + layout.prop(params, 'make_custom_pivot') + ControlLayersOption.TWEAK.parameters_ui(layout, params) diff --git a/rigify/rigs/spines/super_head.py b/rigify/rigs/spines/super_head.py index 9b85e5b5..15f011f7 100644 --- a/rigify/rigs/spines/super_head.py +++ b/rigify/rigs/spines/super_head.py @@ -322,7 +322,10 @@ class Rig(BaseHeadTailRig): def register_parent_bones(self): rig = self.rigify_parent or self builder = SwitchParentBuilder(self.generator) - builder.register_parent(rig, self.bones.org[-1], name='Head', exclude_self=True) + builder.register_parent( + self, self.bones.org[-1], name='Head', + inject_into=rig, exclude_self=True, tags={'head'}, + ) @stage.configure_bones def configure_bbone_chain(self): diff --git a/rigify/rigs/spines/super_spine.py b/rigify/rigs/spines/super_spine.py index 5ed1588e..86021c84 100644 --- a/rigify/rigs/spines/super_spine.py +++ b/rigify/rigs/spines/super_spine.py @@ -81,7 +81,7 @@ class Rig(SubstitutionRig, BoneUtilityMixin): bpy.ops.object.mode_set(mode='OBJECT') # Create the parts - self.assign_params(spine_orgs[0], params_copy, pivot_pos=pivot_pos) + self.assign_params(spine_orgs[0], params_copy, pivot_pos=pivot_pos, make_fk_controls=False) result = [ self.instantiate_rig(basic_spine.Rig, spine_orgs[0]) ] @@ -133,6 +133,8 @@ def add_parameters(params): def parameters_ui(layout, params): """ Create the ui for the rig parameters.""" + layout.label(text="Note: this combined rig is deprecated.", icon='INFO') + r = layout.row(align=True) r.prop(params, "use_head", toggle=True, text="Head") r.prop(params, "use_tail", toggle=True, text="Tail") |