Changeset View
Changeset View
Standalone View
Standalone View
rigify/rigs/chain_rigs.py
- This file was added.
| #====================== 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 itertools import count | |||||
| from ..utils.rig import connected_children_names | |||||
| from ..utils.naming import strip_org, make_derived_name | |||||
| from ..utils.bones import put_bone, flip_bone, flip_bone_chain, is_same_position, is_connected_position | |||||
| from ..utils.bones import copy_bone_position, connect_bbone_chain_handles | |||||
| from ..utils.widgets_basic import create_bone_widget, create_sphere_widget | |||||
| from ..utils.misc import map_list | |||||
| from ..base_rig import BaseRig, stage | |||||
| class SimpleChainRig(BaseRig): | |||||
| """A rig that consists of 3 connected chains of control, org and deform bones.""" | |||||
| def find_org_bones(self, bone): | |||||
| return [bone.name] + connected_children_names(self.obj, bone.name) | |||||
| def initialize(self): | |||||
| if len(self.bones.org) <= 1: | |||||
| self.raise_error("Input to rig type must be a chain of 2 or more bones.") | |||||
| def parent_bones(self): | |||||
| self.rig_parent_bone = self.get_bone_parent(self.bones.org[0]) | |||||
| bbone_segments = None | |||||
| ############################## | |||||
| # BONES | |||||
| # | |||||
| # org[]: | |||||
| # ORG bones | |||||
| # ctrl: | |||||
| # fk[]: | |||||
| # FK control chain. | |||||
| # deform[]: | |||||
| # DEF bones | |||||
| # | |||||
| ############################## | |||||
| ############################## | |||||
| # Control chain | |||||
| @stage.generate_bones | |||||
| def make_control_chain(self): | |||||
| self.bones.ctrl.fk = map_list(self.make_control_bone, count(0), self.bones.org) | |||||
| def make_control_bone(self, i, org): | |||||
| return self.copy_bone(org, make_derived_name(org, 'ctrl'), parent=True) | |||||
| @stage.parent_bones | |||||
| def parent_control_chain(self): | |||||
| self.parent_bone_chain(self.bones.ctrl.fk, use_connect=True) | |||||
| @stage.configure_bones | |||||
| def configure_control_chain(self): | |||||
| for args in zip(count(0), self.bones.ctrl.fk, self.bones.org): | |||||
| self.configure_control_bone(*args) | |||||
| def configure_control_bone(self, i, ctrl, org): | |||||
| self.copy_bone_properties(org, ctrl) | |||||
| @stage.generate_widgets | |||||
| def make_control_widgets(self): | |||||
| for ctrl in self.bones.ctrl.fk: | |||||
| self.make_control_widget(ctrl) | |||||
| def make_control_widget(self, ctrl): | |||||
| create_bone_widget(self.obj, ctrl) | |||||
| ############################## | |||||
| # ORG chain | |||||
| @stage.parent_bones | |||||
| def parent_org_chain(self): | |||||
| pass | |||||
| @stage.rig_bones | |||||
| def rig_org_chain(self): | |||||
| for args in zip(count(0), self.bones.org, self.bones.ctrl.fk): | |||||
| self.rig_org_bone(*args) | |||||
| def rig_org_bone(self, i, org, ctrl): | |||||
| self.make_constraint(org, 'COPY_TRANSFORMS', ctrl) | |||||
| ############################## | |||||
| # Deform chain | |||||
| @stage.generate_bones | |||||
| def make_deform_chain(self): | |||||
| self.bones.deform = map_list(self.make_deform_bone, count(0), self.bones.org) | |||||
| def make_deform_bone(self, i, org): | |||||
| name = self.copy_bone(org, make_derived_name(org, 'def'), parent=True, bbone=True) | |||||
| if self.bbone_segments: | |||||
| self.get_bone(name).bbone_segments = self.bbone_segments | |||||
| return name | |||||
| @stage.parent_bones | |||||
| def parent_deform_chain(self): | |||||
| self.parent_bone_chain(self.bones.deform, use_connect=True) | |||||
| @stage.rig_bones | |||||
| def rig_deform_chain(self): | |||||
| for args in zip(count(0), self.bones.deform, self.bones.org): | |||||
| self.rig_deform_bone(*args) | |||||
| def rig_deform_bone(self, i, deform, org): | |||||
| self.make_constraint(deform, 'COPY_TRANSFORMS', org) | |||||
| class TweakChainRig(SimpleChainRig): | |||||
| """A rig that adds tweak controls to the triple chain.""" | |||||
| ############################## | |||||
| # BONES | |||||
| # | |||||
| # org[]: | |||||
| # ORG bones | |||||
| # ctrl: | |||||
| # fk[]: | |||||
| # FK control chain. | |||||
| # tweak[]: | |||||
| # Tweak control chain. | |||||
| # deform[]: | |||||
| # DEF bones | |||||
| # | |||||
| ############################## | |||||
| ############################## | |||||
| # Tweak chain | |||||
| @stage.generate_bones | |||||
| def make_tweak_chain(self): | |||||
| orgs = self.bones.org | |||||
| self.bones.ctrl.tweak = map_list(self.make_tweak_bone, count(0), orgs + orgs[-1:]) | |||||
| def make_tweak_bone(self, i, org): | |||||
| name = self.copy_bone(org, 'tweak_' + strip_org(org), parent=False, scale=0.5) | |||||
| if i == len(self.bones.org): | |||||
| put_bone(self.obj, name, self.get_bone(org).tail) | |||||
| return name | |||||
| @stage.parent_bones | |||||
| def parent_tweak_chain(self): | |||||
| ctrl = self.bones.ctrl | |||||
| for tweak, main in zip(ctrl.tweak, ctrl.fk + ctrl.fk[-1:]): | |||||
| self.set_bone_parent(tweak, main) | |||||
| @stage.configure_bones | |||||
| def configure_tweak_chain(self): | |||||
| for args in zip(count(0), self.bones.ctrl.tweak): | |||||
| self.configure_tweak_bone(*args) | |||||
| def configure_tweak_bone(self, i, tweak): | |||||
| tweak_pb = self.get_bone(tweak) | |||||
| tweak_pb.rotation_mode = 'ZXY' | |||||
| if i == len(self.bones.org): | |||||
| tweak_pb.lock_rotation_w = True | |||||
| tweak_pb.lock_rotation = (True, True, True) | |||||
| tweak_pb.lock_scale = (True, True, True) | |||||
| else: | |||||
| tweak_pb.lock_rotation_w = False | |||||
| tweak_pb.lock_rotation = (True, False, True) | |||||
| tweak_pb.lock_scale = (False, True, False) | |||||
| @stage.generate_widgets | |||||
| def make_tweak_widgets(self): | |||||
| for tweak in self.bones.ctrl.tweak: | |||||
| self.make_tweak_widget(tweak) | |||||
| def make_tweak_widget(self, tweak): | |||||
| create_sphere_widget(self.obj, tweak) | |||||
| ############################## | |||||
| # ORG chain | |||||
| @stage.rig_bones | |||||
| def rig_org_chain(self): | |||||
| tweaks = self.bones.ctrl.tweak | |||||
| for args in zip(count(0), self.bones.org, tweaks, tweaks[1:]): | |||||
| self.rig_org_bone(*args) | |||||
| def rig_org_bone(self, i, org, tweak, next_tweak): | |||||
| self.make_constraint(org, 'COPY_TRANSFORMS', tweak) | |||||
| if next_tweak: | |||||
| self.make_constraint(org, 'DAMPED_TRACK', next_tweak) | |||||
| self.make_constraint(org, 'STRETCH_TO', next_tweak) | |||||
| class ConnectingChainRig(TweakChainRig): | |||||
| """Chain rig that can attach to an end of the parent, merging bbone chains.""" | |||||
| bbone_segments = 8 | |||||
| use_connect_reverse = None | |||||
| def initialize(self): | |||||
| super().initialize() | |||||
| self.use_connect_chain = self.params.connect_chain | |||||
| self.connected_tweak = None | |||||
| if self.use_connect_chain: | |||||
| first_org = self.bones.org[0] | |||||
| parent = self.rigify_parent | |||||
| parent_orgs = parent.bones.org | |||||
| if not isinstance(parent, SimpleChainRig): | |||||
| self.raise_error("Cannot connect to non-chain parent rig.") | |||||
| ok_reverse = is_same_position(self.obj, parent_orgs[0], first_org) | |||||
| ok_direct = is_connected_position(self.obj, parent_orgs[-1], first_org) | |||||
| if self.use_connect_reverse is None: | |||||
| self.use_connect_reverse = ok_reverse and not ok_direct | |||||
| if not (ok_reverse if self.use_connect_reverse else ok_direct): | |||||
| self.raise_error("Cannot connect chain - bone position is disjoint.") | |||||
| if isinstance(parent, ConnectingChainRig) and parent.use_connect_reverse: | |||||
| self.raise_error("Cannot connect chain - parent is reversed.") | |||||
| def prepare_bones(self): | |||||
| # Exactly match bone position to parent | |||||
| if self.use_connect_chain: | |||||
| first_bone = self.get_bone(self.bones.org[0]) | |||||
| parent_orgs = self.rigify_parent.bones.org | |||||
| if self.use_connect_reverse: | |||||
| first_bone.head = self.get_bone(parent_orgs[0]).head | |||||
| else: | |||||
| first_bone.head = self.get_bone(parent_orgs[-1]).tail | |||||
| def parent_bones(self): | |||||
| # Use the parent of the shared tweak as the rig parent | |||||
| root = self.connected_tweak or self.bones.org[0] | |||||
| self.rig_parent_bone = self.get_bone_parent(root) | |||||
| ############################## | |||||
| # Control chain | |||||
| @stage.parent_bones | |||||
| def parent_control_chain(self): | |||||
| super().parent_control_chain() | |||||
| self.set_bone_parent(self.bones.ctrl.fk[0], self.rig_parent_bone) | |||||
| ############################## | |||||
| # Tweak chain | |||||
| def check_connect_tweak(self, org): | |||||
| """ Check if it is possible to share the last parent tweak control. """ | |||||
| assert self.connected_tweak is None | |||||
| if self.use_connect_chain and isinstance(self.rigify_parent, TweakChainRig): | |||||
| # Share the last tweak bone of the parent rig | |||||
| parent_tweaks = self.rigify_parent.bones.ctrl.tweak | |||||
| index = 0 if self.use_connect_reverse else -1 | |||||
| name = parent_tweaks[index] | |||||
| if not is_same_position(self.obj, name, org): | |||||
| self.raise_error("Cannot connect tweaks - position mismatch.") | |||||
| if not self.use_connect_reverse: | |||||
| copy_bone_position(self.obj, org, name, scale=0.5) | |||||
| name = self.rename_bone(name, 'tweak_' + strip_org(org)) | |||||
| self.connected_tweak = parent_tweaks[index] = name | |||||
| return name | |||||
| else: | |||||
| return None | |||||
| def make_tweak_bone(self, i, org): | |||||
| if i == 0 and self.check_connect_tweak(org): | |||||
| return self.connected_tweak | |||||
| else: | |||||
| return super().make_tweak_bone(i, org) | |||||
| @stage.parent_bones | |||||
| def parent_tweak_chain(self): | |||||
| ctrl = self.bones.ctrl | |||||
| for i, tweak, main in zip(count(0), ctrl.tweak, ctrl.fk + ctrl.fk[-1:]): | |||||
| if i > 0 or not (self.connected_tweak and self.use_connect_reverse): | |||||
| self.set_bone_parent(tweak, main) | |||||
| def configure_tweak_bone(self, i, tweak): | |||||
| super().configure_tweak_bone(i, tweak) | |||||
| if self.use_connect_chain and self.use_connect_reverse and i == len(self.bones.org): | |||||
| tweak_pb = self.get_bone(tweak) | |||||
| tweak_pb.lock_rotation_w = False | |||||
| tweak_pb.lock_rotation = (True, False, True) | |||||
| tweak_pb.lock_scale = (False, True, False) | |||||
| ############################## | |||||
| # ORG chain | |||||
| @stage.parent_bones | |||||
| def parent_org_chain(self): | |||||
| if self.use_connect_chain and self.use_connect_reverse: | |||||
| flip_bone_chain(self.obj, self.bones.org) | |||||
| for org, tweak in zip(self.bones.org, self.bones.ctrl.tweak[1:]): | |||||
| self.set_bone_parent(org, tweak) | |||||
| else: | |||||
| self.set_bone_parent(self.bones.org[0], self.rig_parent_bone) | |||||
| def rig_org_bone(self, i, org, tweak, next_tweak): | |||||
| if self.use_connect_chain and self.use_connect_reverse: | |||||
| self.make_constraint(org, 'DAMPED_TRACK', tweak) | |||||
| self.make_constraint(org, 'STRETCH_TO', tweak) | |||||
| else: | |||||
| super().rig_org_bone(i, org, tweak, next_tweak) | |||||
| ############################## | |||||
| # Deform chain | |||||
| def make_deform_bone(self, i, org): | |||||
| name = super().make_deform_bone(i, org) | |||||
| if self.use_connect_chain and self.use_connect_reverse: | |||||
| self.set_bone_parent(name, None) | |||||
| flip_bone(self.obj, name) | |||||
| return name | |||||
| @stage.parent_bones | |||||
| def parent_deform_chain(self): | |||||
| if self.use_connect_chain: | |||||
| deform = self.bones.deform | |||||
| parent_deform = self.rigify_parent.bones.deform | |||||
| if self.use_connect_reverse: | |||||
| self.set_bone_parent(deform[-1], self.bones.org[-1]) | |||||
| self.parent_bone_chain(reversed(deform), use_connect=True) | |||||
| connect_bbone_chain_handles(self.obj, [ deform[0], parent_deform[0] ]) | |||||
| return | |||||
| else: | |||||
| self.set_bone_parent(deform[0], parent_deform[-1], use_connect=True) | |||||
| super().parent_deform_chain() | |||||
| ############################## | |||||
| # Settings | |||||
| @classmethod | |||||
| def add_parameters(self, params): | |||||
| params.connect_chain = bpy.props.BoolProperty( | |||||
| name='Connect chain', | |||||
| default=False, | |||||
| description='Connect the B-Bone chain to the parent rig' | |||||
| ) | |||||
| @classmethod | |||||
| def parameters_ui(self, layout, params): | |||||
| r = layout.row() | |||||
| r.prop(params, "connect_chain") | |||||