371 lines
12 KiB
Python
371 lines
12 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
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)
|
|
|
|
min_chain_length = 2
|
|
|
|
def initialize(self):
|
|
if len(self.bones.org) < self.min_chain_length:
|
|
self.raise_error("Input to rig type must be a chain of {} or more bones.", self.min_chain_length)
|
|
|
|
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 args in zip(count(0), self.bones.ctrl.fk):
|
|
self.make_control_widget(*args)
|
|
|
|
def make_control_widget(self, i, 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, 'STRETCH_TO', next_tweak, keep_axis='SWING_Y')
|
|
|
|
|
|
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
|
|
|
|
if not isinstance(parent, SimpleChainRig):
|
|
self.raise_error("Cannot connect to non-chain parent rig.")
|
|
|
|
parent_orgs = parent.bones.org
|
|
|
|
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, 'STRETCH_TO', tweak, keep_axis='SWING_Y')
|
|
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")
|