Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
import bpy
import re
import itertools
import bisect
import math
from rigify.utils.errors import MetarigError
from rigify.utils.naming import strip_org, make_derived_name
from rigify.utils.bones import set_bone_widget_transform
from rigify.utils.mechanism import make_driver, make_constraint, driver_var_transform
from rigify.utils.widgets import create_widget
from rigify.utils.widgets_basic import create_circle_widget, create_sphere_widget
from rigify.utils.layers import ControlLayersOption
from rigify.utils.misc import map_list, TypedObject
from rigify.utils.animation import add_generic_snap_fk_to_ik
from rigify.utils.switch_parent import SwitchParentBuilder
from rigify.base_rig import stage
from rigify.rigs.chain_rigs import SimpleChainRig
from rigify.rigs.widgets import create_gear_widget
from typing import NamedTuple
from itertools import count
class Rig(SimpleChainRig):
##############################
# Initialization
name_base: str
name_sep: str
name_suffix: str
spline_name: str
spline_obj: TypedObject[bpy.types.Curve] | None
use_stretch: bool
use_tip: bool
use_fk: bool
use_radius: bool
max_curve_radius: float
org_lengths: list[float]
org_tot_lengths: list[float]
chain_length: float
avg_length: float
class PosSpec(NamedTuple):
org_index: int
head_tail: float
name: str
num_main_controls: int
main_control_pos_list: list['Rig.PosSpec']
start_control_pos_list: list['Rig.PosSpec']
end_control_pos_list: list['Rig.PosSpec']
def initialize(self):
super().initialize()
org_chain = self.bones.org
org_bones = [self.get_bone(org) for org in org_chain]
# Compute master bone name: inherit .LR suffix, but strip trailing digits
name_parts = re.match(r'^(.*?)(?:([._-])\d+)?((?:[._-][LlRr])?)(?:\.\d+)?$',
strip_org(org_chain[0]))
name_base, name_sep, name_suffix = name_parts.groups()
self.name_base = name_base
self.name_sep = name_sep if name_sep else '-'
self.name_suffix = name_suffix
# Find the spline object if it exists
self.spline_name = (self.obj.name + '-MCH-' + name_base + self.name_sep +
'spline' + name_suffix)
self.spline_obj = None
if self.spline_name in bpy.data.objects:
self.spline_obj = bpy.data.objects[self.spline_name]
if not isinstance(self.spline_obj.data, bpy.types.Curve):
raise MetarigError(
f"Object '{self.spline_name}' already exists and is not a curve.")
if self.spline_obj.parent and self.spline_obj.parent != self.obj:
raise MetarigError(
f"Object '{self.spline_name}' already exists and is not a child of the rig.")
# Options
self.use_stretch = (self.params.sik_stretch_control == 'MANUAL_STRETCH')
self.use_tip = (self.params.sik_stretch_control == 'DIRECT_TIP')
self.use_fk = self.params.sik_fk_controls
# Compute org chain lengths and control distribution
if self.use_tip:
org_bones.pop()
self.org_lengths = [bone.length for bone in org_bones]
self.org_tot_lengths = list(itertools.accumulate(self.org_lengths))
self.chain_length = self.org_tot_lengths[-1]
self.avg_length = self.chain_length / len(org_bones)
end_idx = len(org_bones) - 1
# Find which bones hold main controls
self.num_main_controls = self.params.sik_mid_controls + 2
main_control_step = self.chain_length / (self.num_main_controls - 1)
self.main_control_pos_list = [
self.find_bone_by_length(
i * main_control_step, name=self.get_main_control_name(i))
for i in range(self.num_main_controls)
]
# Likewise for extra start and end controls
num_start_controls = self.params.sik_start_controls
start_range = main_control_step / self.org_lengths[0]
start_control_step = start_range * 0.001 / max(1, num_start_controls)
self.start_control_pos_list = [
self.PosSpec(
0, (i + 1) * start_control_step, self.make_name('start%02d' % (idx+1)))
for idx, i in enumerate(reversed(range(num_start_controls)))
]
num_end_controls = self.params.sik_end_controls + (1 if self.use_tip else 0)
end_range = main_control_step / self.org_lengths[-1]
end_control_step = end_range * 0.001 / max(1, num_end_controls)
self.end_control_pos_list = [
self.PosSpec(
end_idx, 1.0 - (i + 1) * end_control_step, self.make_name('end%02d' % (idx+1)))
for idx, i in enumerate(reversed(range(num_end_controls)))
]
# Adjust control bindings if using manual tip control
if self.use_tip:
# tip = self.main_control_pos_list[-1]
self.main_control_pos_list[-1] = self.PosSpec(end_idx + 1, 0, strip_org(org_chain[-1]))
# tip_extra = self.end_control_pos_list[0]
self.end_control_pos_list[0] = self.PosSpec(
end_idx, max(0.0, 1 - end_range * 0.25), self.make_name('end'))
# Radius scaling
self.use_radius = self.params.sik_radius_scaling
self.max_curve_radius = self.params.sik_max_radius if self.use_radius else 1.0
##############################
# Utilities
def find_bone_by_length(self, pos: float, name: str):
tot_lengths = self.org_tot_lengths
idx = bisect.bisect_left(tot_lengths, pos)
idx = min(idx, len(tot_lengths) - 1)
prev = tot_lengths[idx - 1] if idx > 0 else 0
return self.PosSpec(idx, min(1.0, (pos - prev) / (tot_lengths[idx] - prev)), name)
def make_name(self, mid_part: str):
"""Make a name for a bone not tied to a specific org bone"""
return self.name_base + self.name_sep + mid_part + self.name_suffix
def get_main_control_name(self, i: int):
if i == 0:
base = 'start'
elif i == self.num_main_controls - 1:
base = 'end'
else:
base = 'mid%02d' % i
return self.make_name(base)
def make_bone_by_spec(self, pos_spec: 'Rig.PosSpec', name: str, scale: float):
"""Make a bone positioned along the chain."""
org_name = self.bones.org[pos_spec[0]]
new_name = self.copy_bone(org_name, name, parent=False)
org_bone = self.get_bone(org_name)
new_bone = self.get_bone(new_name)
new_bone.translate(pos_spec[1] * (org_bone.tail - org_bone.head))
new_bone.length = self.avg_length * scale
return new_name
ENABLE_CONTROL_PROPERTY = [None, 'start_controls', 'end_controls']
def rig_enable_control_driver(self, owner, prop: str, subtype: int, index: int, disable=False):
if subtype != 0:
if self.use_tip and subtype == 2:
if index == 0:
return
index -= 1
master = self.bones.ctrl.master
var_prop = self.ENABLE_CONTROL_PROPERTY[subtype]
make_driver(
owner, prop,
expression='active %s %d' % ('<=' if disable else '>', index),
variables={'active': (self.obj, master, var_prop)}
)
##############################
# BONES
class CtrlBones(SimpleChainRig.CtrlBones):
master: str # Root control for moving and scaling the whole rig.
main: list[str] # List of main spline controls (always visible and active).
start: list[str] # List of extra spline controls attached to the
end: list[str] # tip main ones (can disable).
end_twist: str # Twist control at the end of the tentacle.
class MchBones(SimpleChainRig.MchBones):
start_parent: str # Intermediate bones for parenting extra controls
end_parent: str # - discards scale of the main.
start_hooks: list[str] # Proxy bones for extra control hooks.
end_hooks: list[str]
ik: list[str] # Spline IK chain, extracting the shape of the curve.
ik_final: list[str] # Final IK result with tip_fix.
end_stretch: str # Bone used in distributing the end twist control scaling.
tip_fix_parent: str # Bones used to match tip control rotation and scale.
tip_fix: str
bones: SimpleChainRig.ToplevelBones[
list[str],
'Rig.CtrlBones',
'Rig.MchBones',
list[str]
]
##############################
# Master control bone
@stage.generate_bones
def make_master_control(self):
self.bones.ctrl.master = self.copy_bone(
self.bones.org[0], self.make_name('master'), parent=True,
length=self.avg_length * 1.5
)
self.register_parents()
def register_parents(self):
builder = SwitchParentBuilder(self.generator)
builder.register_parent(self, self.bones.ctrl.master)
if self.use_tip:
builder.register_parent(self, self.bones.org[-1], exclude_self=True)
@stage.configure_bones
def configure_master_control(self):
master = self.bones.ctrl.master
ctrls = self.bones.ctrl.flatten()
rig_name = self.name_base + self.name_suffix
panel = self.script.panel_with_selected_check(self, ctrls)
# Properties for enabling extra controls
if self.params.sik_start_controls > 0:
self.make_property(
master, 'start_controls', 0,
min=0, max=self.params.sik_start_controls,
description="Enabled extra start controls for "+rig_name
)
panel.custom_prop(master, 'start_controls', text="Start Controls")
if self.params.sik_end_controls > 0:
self.make_property(
master, 'end_controls', 0,
min=0, max=self.params.sik_end_controls,
description="Enabled extra end controls for "+rig_name
)
panel.custom_prop(master, 'end_controls', text="End Controls")
# End twist correction for directly controllable tip
if self.use_tip:
max_val = len(self.bones.org) / 2
self.make_property(
master, 'end_twist', 0.0, min=-max_val, max=max_val,
description="Rough end twist estimate in full rotations. The rig auto-corrects "
"it to the actual tip orientation within 180 degrees"
)
panel.custom_prop(master, 'end_twist', text="End Twist Fix")
# IK/FK switch
if self.use_fk:
self.make_property(master, 'IK_FK', 0.0, description='IK/FK switch for '+rig_name)
panel.custom_prop(master, 'IK_FK', text="IK - FK", slider=True)
ik_controls = [item[0] for item in self.all_controls]
if not self.use_tip:
ik_controls += [self.bones.ctrl.end_twist]
add_generic_snap_fk_to_ik(
panel,
fk_bones=self.bones.ctrl.fk, ik_bones=self.get_ik_final(),
ik_ctrl_bones=ik_controls,
undo_copy_scale=True,
rig_name=rig_name
)
@stage.generate_widgets
def make_master_control_widget(self):
master_name = self.bones.ctrl.master
create_gear_widget(self.obj, master_name, radius=0.5)
##############################
# Twist controls
@stage.generate_bones
def make_twist_control_bones(self):
if not self.use_tip:
self.bones.ctrl.end_twist = self.make_twist_control_bone('end-twist', 1.15)
def make_twist_control_bone(self, name, size):
return self.copy_bone(self.bones.org[0], self.make_name(name),
length=self.avg_length * size)
@stage.parent_bones
def parent_twist_control_bones(self):
if not self.use_tip:
self.set_bone_parent(self.bones.ctrl.end_twist, self.bones.ctrl.master)
@stage.configure_bones
def configure_twist_control_bones(self):
if not self.use_tip:
self.configure_twist_control_bone(self.bones.ctrl.end_twist)
def configure_twist_control_bone(self, name):
bone = self.get_bone(name)
bone.rotation_mode = 'XYZ'
bone.lock_location = (True, True, True)
bone.lock_rotation = (True, False, True)
if not self.use_stretch:
bone.lock_scale = (True, True, True)
@stage.rig_bones
def rig_twist_control_bones(self):
if not self.use_tip:
# Copy the location of the end bone to provide more convenient tool behavior.
self.make_constraint(self.bones.ctrl.end_twist, 'COPY_LOCATION', self.bones.org[-1])
@stage.generate_widgets
def make_twist_control_widgets(self):
if not self.use_tip:
self.make_twist_control_widget(self.bones.ctrl.end_twist, self.bones.org[-1], 0.85)
def make_twist_control_widget(self, ctrl, org, size=1.0, head_tail=0.5):
set_bone_widget_transform(self.obj, ctrl, org, target_size=True)
create_twist_widget(self.obj, ctrl, size=size, head_tail=head_tail)
##############################
# Twist controls MCH
@stage.generate_bones
def make_mch_twist_control_bones(self):
if self.use_stretch:
self.bones.mch.end_stretch = self.make_mch_end_stretch_bone('end-twist.stretch', 1.15)
def make_mch_end_stretch_bone(self, name_base, size):
name = make_derived_name(self.make_name(name_base), 'mch')
return self.copy_bone(self.bones.org[0], name, length=self.avg_length * size * 0.5)
@stage.parent_bones
def parent_mch_twist_control_bones(self):
if self.use_stretch:
self.set_bone_parent(self.bones.mch.end_stretch, self.bones.ctrl.master)
@stage.rig_bones
def rig_mch_twist_control_bones(self):
if self.use_stretch:
self.rig_mch_end_stretch_bone(self.bones.mch.end_stretch, self.bones.ctrl.end_twist)
def rig_mch_end_stretch_bone(self, mch, ctrl):
# Break the dependency cycle caused by COPY_LOCATION above by copying raw properties.
self.make_driver(mch, 'scale', index=0, variables=[(ctrl, '.scale.x')])
self.make_driver(mch, 'scale', index=1, variables=[(ctrl, '.scale.y')])
self.make_driver(mch, 'scale', index=2, variables=[(ctrl, '.scale.z')])
self.make_constraint(mch, 'MAINTAIN_VOLUME', mode='UNIFORM', owner_space='LOCAL')
##############################
# Spline controls
class ControlEntry(NamedTuple):
bone: str
subtype: int
index: int
tip_controls_table: list[str | None]
all_controls: list['Rig.ControlEntry']
@stage.generate_bones
def make_main_control_chain(self):
self.bones.ctrl.main = map_list(self.make_main_control_bone, self.main_control_pos_list)
self.bones.ctrl.start = map_list(self.make_extra_control_bone, self.start_control_pos_list)
self.bones.ctrl.end = map_list(self.make_extra_control_bone, self.end_control_pos_list)
self.make_all_controls_list()
self.make_controls_switch_parent()
def make_all_controls_list(self):
main_controls = [self.ControlEntry(bone, 0, i)
for i, bone in enumerate(self.bones.ctrl.main)]
start_controls = [self.ControlEntry(bone, 1, i)
for i, bone in enumerate(self.bones.ctrl.start)]
end_controls = [self.ControlEntry(bone, 2, i)
for i, bone in enumerate(self.bones.ctrl.end)]
self.tip_controls_table = [None, self.bones.ctrl.main[0], self.bones.ctrl.main[-1]]
self.all_controls = [main_controls[0], *reversed(start_controls),
*main_controls[1:-1],
*end_controls, main_controls[-1]]
def make_controls_switch_parent(self):
builder = SwitchParentBuilder(self.generator)
def extra():
return [
(self.bones.mch.start_parent, self.bones.ctrl.main[0]),
(self.bones.mch.end_parent, self.bones.ctrl.main[-1])
]
select_table = [
lambda: self.bones.ctrl.master,
lambda: self.bones.mch.start_parent,
lambda: self.bones.mch.end_parent
]
for (bone, subtype, index) in self.all_controls[1:-1]:
builder.build_child(
self, bone, extra_parents=extra,
select_parent=select_table[subtype],
no_fix_rotation=True, no_fix_scale=True
)
builder.build_child(self, self.bones.ctrl.main[-1], no_fix_scale=not self.use_tip)
def make_main_control_bone(self, pos_spec):
return self.make_bone_by_spec(pos_spec, pos_spec[2], 1.1)
def make_extra_control_bone(self, pos_spec):
return self.make_bone_by_spec(pos_spec, pos_spec[2], 0.9)
@stage.parent_bones
def parent_main_control_chain(self):
self.set_bone_parent(self.bones.ctrl.main[0], self.bones.ctrl.master)
@stage.configure_bones
def configure_main_control_chain(self):
for info in self.all_controls:
self.configure_main_control_bone(*info)
def configure_main_control_bone(self, ctrl, subtype, index):
bone = self.get_bone(ctrl)
if subtype == 0 and index == 0:
if self.params.sik_start_controls > 0:
bone.rotation_mode = 'QUATERNION'
else:
bone.rotation_mode = 'XYZ'
bone.lock_rotation = (True, False, True)
elif (subtype == 0 and index == self.num_main_controls-1
and self.params.sik_end_controls > 0):
bone.rotation_mode = 'QUATERNION'
else:
bone.lock_rotation_w = True
bone.lock_rotation = (True, True, True)
if subtype == 0 and index == 0:
bone.lock_scale = (False, not self.use_stretch, False)
elif not self.use_radius:
bone.lock_scale = (True, True, True)
@stage.rig_bones
def rig_main_control_chain(self):
for info in self.all_controls:
self.rig_main_control_bone(*info)
def rig_main_control_bone(self, ctrl, subtype, index):
if self.use_stretch and subtype == 0 and index == 0:
self.make_constraint(ctrl, 'MAINTAIN_VOLUME', mode='UNIFORM', owner_space='LOCAL')
self.rig_enable_control_driver(
self.get_bone(ctrl).bone, 'hide', subtype, index, disable=True)
@stage.generate_widgets
def make_main_control_widgets(self):
for info in self.all_controls:
self.make_main_control_widget(*info)
def make_main_control_widget(self, ctrl, subtype, index):
if subtype == 0 and index == 0:
if len(self.start_control_pos_list) > 0:
create_twist_widget(self.obj, ctrl, size=1, head_tail=0.25)
else:
self.make_twist_control_widget(ctrl, self.bones.org[0], head_tail=0.25)
elif self.use_tip and subtype == 0 and index == self.num_main_controls - 1:
create_circle_widget(self.obj, ctrl, radius=0.5, head_tail=0.25)
else:
create_sphere_widget(self.obj, ctrl)
##############################
# FK Control chain
@stage.generate_bones
def make_control_chain(self):
if self.use_fk:
super().make_control_chain()
@stage.parent_bones
def parent_control_chain(self):
if self.use_fk:
super().parent_control_chain()
self.set_bone_parent(self.bones.ctrl.fk[0], self.bones.ctrl.master)
@stage.configure_bones
def configure_control_chain(self):
if self.use_fk:
super().configure_control_chain()
ControlLayersOption.FK.assign(self.params, self.obj, self.bones.ctrl.fk)
@stage.rig_bones
def rig_control_chain(self):
if self.use_fk:
for args in zip(self.bones.ctrl.fk, [None, *self.bones.ctrl.fk]):
self.rig_control_bone(*args)
def rig_control_bone(self, fk, fk_prev):
if fk_prev:
self.get_bone(fk).bone.use_inherit_scale = False
self.make_constraint(
fk, 'COPY_SCALE', fk_prev, use_offset=True, space='POSE', name='Parent Scale')
@stage.generate_widgets
def make_control_widgets(self):
if self.use_fk:
super().make_control_widgets()
def make_control_widget(self, i, ctrl):
create_circle_widget(self.obj, ctrl, radius=0.3, head_tail=0.5)
##############################
# Spline tip parent MCH
@stage.generate_bones
def make_mch_extra_parent_bones(self):
self.bones.mch.start_parent = \
self.make_mch_extra_parent_bone(self.main_control_pos_list[0])
self.bones.mch.end_parent = \
self.make_mch_extra_parent_bone(self.main_control_pos_list[-1])
def make_mch_extra_parent_bone(self, pos_spec):
return self.make_bone_by_spec(
pos_spec, make_derived_name(pos_spec[2], 'mch', '.psocket'), 0.40)
@stage.parent_bones
def parent_mch_extra_parent_bones(self):
self.set_bone_parent(self.bones.mch.start_parent, self.bones.ctrl.master)
self.set_bone_parent(self.bones.mch.end_parent, self.bones.ctrl.master)
@stage.rig_bones
def rig_mch_extra_parent_bones(self):
self.rig_mch_extra_parent_bone(self.bones.mch.start_parent, self.bones.ctrl.main[0])
self.rig_mch_extra_parent_bone(self.bones.mch.end_parent, self.bones.ctrl.main[-1])
def rig_mch_extra_parent_bone(self, bone, ctrl):
self.make_constraint(bone, 'COPY_LOCATION', ctrl)
self.make_constraint(bone, 'COPY_ROTATION', ctrl)
##############################
# Spline extra hook proxy MCH
mch_hooks_table: list[None | str]
@stage.generate_bones
def make_mch_extra_hook_bones(self):
self.bones.mch.start_hooks = map_list(
self.make_mch_extra_hook_bone, self.start_control_pos_list)
self.bones.mch.end_hooks = map_list(
self.make_mch_extra_hook_bone, self.end_control_pos_list)
self.mch_hooks_table = [None, self.bones.mch.start_hooks, self.bones.mch.end_hooks]
def make_mch_extra_hook_bone(self, pos_spec):
return self.make_bone_by_spec(
pos_spec, make_derived_name(pos_spec[2], 'mch', '.hook'), 0.30)
@stage.parent_bones
def parent_mch_extra_hook_bones(self):
for hook in self.bones.mch.start_hooks:
self.set_bone_parent(hook, self.bones.mch.start_parent)
for hook in self.bones.mch.end_hooks:
self.set_bone_parent(hook, self.bones.mch.end_parent)
@stage.rig_bones
def rig_mch_extra_hook_bones(self):
for (bone, subtype, index) in self.all_controls:
hooks = self.mch_hooks_table[subtype]
if hooks:
self.rig_mch_extra_hook_bone(hooks[index], bone, subtype, index)
def rig_mch_extra_hook_bone(self, hook, ctrl, subtype, index):
tip_ctrl = self.tip_controls_table[subtype]
con = self.make_constraint(hook, 'COPY_LOCATION', ctrl)
self.rig_enable_control_driver(con, 'mute', subtype, index, disable=True)
con = self.make_constraint(hook, 'COPY_SCALE', ctrl, space='LOCAL')
self.rig_enable_control_driver(con, 'mute', subtype, index, disable=True)
if subtype == 2 and not self.use_tip:
con = self.make_constraint(hook, 'COPY_SCALE', tip_ctrl, space='LOCAL')
self.rig_enable_control_driver(con, 'mute', subtype, index, disable=False)
##############################
# Spline Object
@stage.configure_bones
def make_spline_object(self):
if not self.spline_obj:
spline_data = bpy.data.curves.new(self.spline_name, 'CURVE')
new_spline = bpy.data.objects.new(self.spline_name, spline_data)
self.spline_obj = new_spline # noqa
self.generator.collection.objects.link(self.spline_obj)
self.spline_obj.show_in_front = True
self.spline_obj.hide_select = True
self.spline_obj.hide_render = True
# self.spline_obj.hide_viewport = True
self.spline_obj.animation_data_clear()
self.spline_obj.data.animation_data_clear()
self.spline_obj.shape_key_clear()
self.spline_obj.modifiers.clear()
spline_data = self.spline_obj.data
spline_data.splines.clear()
spline_data.dimensions = '3D'
self.make_spline_points(spline_data, self.all_controls)
if self.use_radius:
self.make_spline_keys(self.spline_obj, self.all_controls)
self.spline_obj.parent = self.obj
self.spline_obj.parent_type = 'OBJECT'
def make_spline_points(self, spline_data, all_controls):
spline = spline_data.splines.new('BEZIER')
spline.bezier_points.add(len(all_controls) - 1)
end_index = len(all_controls) - 1
for i, (name, subtype, index) in enumerate(all_controls):
point = spline.bezier_points[i]
point.handle_left_type = point.handle_right_type = 'AUTO'
point.co = point.handle_left = point.handle_right = self.get_bone(name).head
if i == 0 or self.use_tip and i == end_index:
point.radius = 1.0
else:
point.radius = self.max_curve_radius
def make_spline_keys(self, spline_obj, all_controls):
spline_obj.shape_key_add(name='Basis', from_mix=False)
controls = all_controls[1:-1] if self.use_tip else all_controls[1:]
for i, (name, subtype, index) in enumerate(controls):
key = spline_obj.shape_key_add(name=name, from_mix=False)
key.value = 0.0
key.data[i+1].radius = 0.0
@stage.rig_bones
def rig_spline_object(self):
for i, info in enumerate(self.all_controls):
self.rig_spline_hook(i, *info)
if self.use_radius:
controls = self.all_controls[1:-1] if self.use_tip else self.all_controls[1:]
for i, info in enumerate(controls):
self.rig_spline_radius_shapekey(i, *info)
def rig_spline_hook(self, i, ctrl, subtype, index):
hooks = self.mch_hooks_table[subtype]
bone = self.get_bone(ctrl)
hook = self.spline_obj.modifiers.new(ctrl, 'HOOK')
assert isinstance(hook, bpy.types.HookModifier)
hook.object = self.obj
hook.subtarget = hooks[index] if hooks else ctrl
hook.center = bone.head
hook.vertex_indices_set([i*3, i*3 + 1, i*3 + 2])
def rig_spline_radius_shapekey(self, i, ctrl, subtype, index):
key = self.spline_obj.data.shape_keys.key_blocks[i + 1]
assert key.name == ctrl
hooks = self.mch_hooks_table[subtype]
target = hooks[index] if hooks else ctrl
expr = '1 - var / %.2f' % self.max_curve_radius
scale_var = [driver_var_transform(self.obj, target, type='SCALE_AVG', space='LOCAL')]
make_driver(key, 'value', expression=expr, variables=scale_var)
##############################
# Spline IK Chain MCH
@stage.generate_bones
def make_mch_ik_chain(self):
orgs = self.bones.org[0:-1] if self.use_tip else self.bones.org
self.bones.mch.ik = map_list(self.make_mch_ik_bone, orgs)
def make_mch_ik_bone(self, org):
name = self.copy_bone(org, make_derived_name(org, 'mch', '.ik'))
self.get_bone(name).use_inherit_scale = False
return name
@stage.parent_bones
def parent_mch_ik_chain(self):
self.parent_bone_chain(self.bones.mch.ik, use_connect=True)
self.set_bone_parent(self.bones.mch.ik[0], self.bones.ctrl.main[0])
@stage.rig_bones
def rig_mch_ik_chain(self):
for i, args in enumerate(zip(self.bones.mch.ik)):
self.rig_mch_ik_bone(i, *args)
self.rig_mch_ik_constraint(self.bones.mch.ik[-1])
def rig_mch_ik_bone(self, i, mch):
self.get_bone(mch).rotation_mode = 'XYZ'
num_ik = len(self.bones.org)
# Apply end twist rotation
if self.use_tip:
rot_fac = math.pi * 2 / num_ik
rot_var = [(self.bones.ctrl.master, 'end_twist')]
else:
rot_fac = 1.0 / num_ik
rot_var = [(self.bones.ctrl.end_twist, '.rotation_euler.y')]
self.make_driver(
mch, 'rotation_euler', index=1, expression='var * %f' % rot_fac, variables=rot_var)
# Copy the common scale
self.make_constraint(mch, 'COPY_SCALE', self.bones.ctrl.main[0])
if self.use_stretch:
self.make_constraint(
mch, 'COPY_SCALE', self.bones.mch.end_stretch,
use_offset=True, space='LOCAL',
power=(i + 1) / num_ik
)
def rig_mch_ik_constraint(self, mch):
ik_bone = self.get_bone(mch)
make_constraint(
ik_bone, 'SPLINE_IK', self.spline_obj,
chain_count=len(self.bones.mch.ik),
use_curve_radius=self.use_radius,
y_scale_mode='BONE_ORIGINAL' if self.use_stretch else 'FIT_CURVE',
xz_scale_mode='VOLUME_PRESERVE',
use_original_scale=True,
)
##############################
# Tip matching MCH
@stage.generate_bones
def make_mch_tip_fix(self):
if self.use_tip:
org = self.bones.org[-1]
parent = self.copy_bone(org, make_derived_name(org, 'mch', '.fix.parent'), scale=0.8)
self.get_bone(parent).use_inherit_scale = False
self.bones.mch.tip_fix_parent = parent
self.bones.mch.tip_fix = self.copy_bone(
org, make_derived_name(org, 'mch', '.fix'), scale=0.7)
@stage.parent_bones
def parent_mch_tip_fix(self):
if self.use_tip:
self.set_bone_parent(self.bones.mch.tip_fix_parent, self.bones.mch.ik[-1])
self.set_bone_parent(self.bones.mch.tip_fix, self.bones.mch.tip_fix_parent)
@stage.rig_bones
def rig_mch_tip_fix(self):
if self.use_tip:
ctrl = self.bones.ctrl.main[-1]
parent = self.bones.mch.tip_fix_parent
fix = self.bones.mch.tip_fix
# Rig the baseline bone as the end of the IK chain (scale, twist)
self.rig_mch_ik_bone(len(self.bones.mch.ik), parent)
# Align the baseline to the tip control direction
self.make_constraint(parent, 'DAMPED_TRACK', ctrl, head_tail=1.0)
# Deduce the scale and twist correction by subtracting baseline
# from tip control transform via parenting and local space.
self.make_constraint(fix, 'COPY_TRANSFORMS', ctrl)
###################################
# Final IK Chain MCH (tip matched)
@stage.generate_bones
def make_mch_ik_final_chain(self):
if self.use_tip:
self.bones.mch.ik_final = map_list(self.make_mch_ik_final_bone, self.bones.org[0:-1])
def make_mch_ik_final_bone(self, org):
return self.copy_bone(org, make_derived_name(org, 'mch', '.ik.final'))
def get_ik_final(self):
if self.use_tip:
return [*self.bones.mch.ik_final, self.bones.ctrl.main[-1]]
else:
return self.bones.mch.ik
@stage.parent_bones
def parent_mch_ik_final_chain(self):
if self.use_tip:
for final, ik in zip(self.bones.mch.ik_final, self.bones.mch.ik):
self.set_bone_parent(final, ik)
@stage.rig_bones
def rig_mch_ik_final_chain(self):
if self.use_tip:
for args in zip(count(0), self.bones.mch.ik_final):
self.rig_mch_ik_final_bone(*args)
def rig_mch_ik_final_bone(self, i, mch):
fix = self.bones.mch.tip_fix
factor = (i + 1) / len(self.bones.org)
self.make_constraint(
mch, 'COPY_ROTATION', fix, space='LOCAL',
use_x=False, use_z=False, influence=factor
)
self.make_constraint(
mch, 'COPY_SCALE', fix, space='LOCAL',
use_y=False, power=factor
)
##############################
# ORG chain
@stage.parent_bones
def parent_org_chain(self):
self.set_bone_parent(self.bones.org[0], self.bones.ctrl.master)
@stage.rig_bones
def rig_org_chain(self):
for args in zip(count(0), self.bones.org, self.get_ik_final()):
self.rig_org_bone(*args)
def rig_org_bone(self, i, org, ik):
self.make_constraint(org, 'COPY_TRANSFORMS', ik)
if self.use_fk:
con = self.make_constraint(org, 'COPY_TRANSFORMS', self.bones.ctrl.fk[i])
self.make_driver(con, 'influence', variables=[(self.bones.ctrl.master, 'IK_FK')])
##############################
# UI
@classmethod
def add_parameters(cls, params):
""" Register the rig parameters. """
params.sik_start_controls = bpy.props.IntProperty(
name="Extra Start Controls", min=0, default=1,
description="Number of extra spline control points attached to the start control"
)
params.sik_mid_controls = bpy.props.IntProperty(
name="Middle Controls", min=1, default=1,
description="Number of spline control points in the middle"
)
params.sik_end_controls = bpy.props.IntProperty(
name="Extra End Controls", min=0, default=1,
description="Number of extra spline control points attached to the end control"
)
params.sik_stretch_control = bpy.props.EnumProperty(
name="Tip Control",
description="How the stretching of the tentacle is controlled",
items=[('FIT_CURVE', 'Stretch To Fit', 'The tentacle stretches to fit the curve'),
('DIRECT_TIP', 'Direct Tip Control',
'The last bone of the chain is directly controlled, like the hand in an IK '
'arm, and the middle stretches to reach it'),
('MANUAL_STRETCH', 'Manual Squash & Stretch',
'The tentacle scaling is manually controlled via twist controls.')]
)
params.sik_radius_scaling = bpy.props.BoolProperty(
name="Radius Scaling", default=True,
description="Allow scaling the spline control bones to affect the thickness via "
"curve radius"
)
params.sik_max_radius = bpy.props.FloatProperty(
name="Maximum Radius", min=1, default=10,
description="Maximum supported scale factor for the spline control bones"
)
params.sik_fk_controls = bpy.props.BoolProperty(
name="FK Controls", default=True,
description="Generate an FK control chain for the tentacle"
)
ControlLayersOption.FK.add_parameters(params)
@classmethod
def parameters_ui(cls, layout, params):
""" Create the ui for the rig parameters. """
layout.label(icon='INFO', text='A straight line rest shape works best.')
layout.prop(params, 'sik_start_controls')
layout.prop(params, 'sik_mid_controls')
layout.prop(params, 'sik_end_controls')
layout.prop(params, 'sik_stretch_control', text='')
layout.prop(params, 'sik_radius_scaling')
col = layout.column()
col.active = params.sik_radius_scaling
col.prop(params, 'sik_max_radius')
layout.prop(params, 'sik_fk_controls')
col = layout.column()
col.active = params.sik_fk_controls
ControlLayersOption.FK.parameters_ui(col, params)
def create_twist_widget(rig, bone_name, size=1.0, head_tail=0.5, bone_transform_name=None):
obj = create_widget(rig, bone_name, bone_transform_name)
if obj is not None:
verts = [(0.3429814279079437*size, head_tail, 0.22917263209819794*size),
(0.38110050559043884*size, head_tail-0.05291016772389412*size, 0.1578568667*size),
(0.40457412600517273*size, head_tail-0.05291016772389412*size, 0.0804747119*size),
(0.41250014305114746*size, head_tail-0.05291016772389412*size, 0.0),
(0.40457412600517273*size, head_tail-0.05291016772389412*size, -0.080474764*size),
(0.38110050559043884*size, head_tail-0.05291016772389412*size, -0.157856911*size),
(0.3429814279079437*size, head_tail, -0.22917278110980988*size),
(0.22917293012142181*size, head_tail, -0.3429813086986542*size),
(0.1578570008277893*size, head_tail-0.05291016772389412*size, -0.3811003565*size),
(0.0804748609662056*size, head_tail-0.05291016772389412*size, -0.4045739769*size),
(0.0, head_tail-0.05291026830673218*size, -0.4124999940395355*size),
(-0.080474711954593*size, head_tail-0.052910167723892*size, -0.40457397699*size),
(-0.15785688161849*size, head_tail-0.05291016772394*size, -0.38110026717974*size),
(-0.22917267680168152*size, head_tail, -0.3429811894893646*size),
(-0.34298115968704224*size, head_tail, -0.22917254269123077*size),
(-0.38110023736953*size, head_tail-0.05291016772389*size, -0.15785665810108*size),
(-0.40457373857498*size, head_tail-0.05291016772389*size, -0.08047446608543*size),
(-0.4124998152256012*size, head_tail-0.05291016772389412*size, 0.0),
(-0.40457355976104*size, head_tail-0.05291016772389*size, 0.080475136637687*size),
(-0.38109982013702*size, head_tail-0.05291016772389*size, 0.157857269048690*size),
(-0.34298068284988403*size, head_tail, 0.22917301952838898*size),
(-0.2291719913482666*size, head_tail, 0.34298139810562134*size),
(-0.15785618126392*size, head_tail-0.05291016772389*size, 0.38110047578811*size),
(-0.08047392964363*size, head_tail-0.05291016772389*size, 0.40457388758659*size),
(0.0, head_tail-0.05291016772389412*size, 0.41249993443489075*size),
(0.080475620925426*size, head_tail-0.05291016772389*size, 0.40457367897033*size),
(0.157857790589332*size, head_tail-0.05291016772389*size, 0.38109987974166*size),
(0.22917351126670837*size, head_tail, 0.3429807126522064*size),
(0.381100505590438*size, head_tail+0.05290994420647*size, 0.15785686671733*size),
(0.404574126005172*size, head_tail+0.05290994420647*size, 0.08047470450401*size),
(0.41250014305114746*size, head_tail+0.05290994420647621*size, 0.0),
(0.404574126005172*size, head_tail+0.05290994420647*size, -0.0804747715592*size),
(0.381100505590438*size, head_tail+0.05290994420647*size, -0.1578569114208*size),
(0.157857000827789*size, head_tail+0.05290994420647*size, -0.3811003565788*size),
(0.080474860966205*size, head_tail+0.05290994420647*size, -0.4045739769935*size),
(0.0, head_tail+0.05290984362363815*size, -0.4124999940395355*size),
(-0.08047471195459*size, head_tail+0.05290994420647*size, -0.4045739769935*size),
(-0.15785688161849*size, head_tail+0.05290994420647*size, -0.38110026717185*size),
(-0.38110023736953*size, head_tail+0.05290994420647*size, -0.15785665810108*size),
(-0.40457373857498*size, head_tail+0.05290994420647*size, -0.08047447353601*size),
(-0.41249981522560*size, head_tail+0.05290994420647*size, 0.0),
(-0.40457355976104*size, head_tail+0.05290994420647*size, 0.080475129187107*size),
(-0.38109982013702*size, head_tail+0.05290994420647*size, 0.157857269048690*size),
(-0.15785618126392*size, head_tail+0.05290994420647*size, 0.381100475788116*size),
(-0.08047392964363*size, head_tail+0.05290994420647*size, 0.404573887586593*size),
(0.0, head_tail+0.05290994420647621*size, 0.41249993443489075*size),
(0.080475620925426*size, head_tail+0.05290994420647*size, 0.404573678970339*size),
(0.157857790589332*size, head_tail+0.05290994420647*size, 0.381099879741667*size)]
edges = [(1, 0), (2, 1), (2, 3), (3, 4), (5, 4), (5, 6), (7, 8), (9, 8), (10, 9), (10, 11),
(12, 11), (12, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (20, 19),
(28, 0), (21, 22), (23, 22), (23, 24), (24, 25), (26, 25), (26, 27), (47, 27),
(29, 28), (29, 30), (30, 31), (32, 31), (32, 6), (34, 33), (35, 34), (35, 36),
(37, 36), (7, 33), (37, 13), (39, 38), (39, 40), (40, 41), (42, 41), (14, 38),
(20, 42), (44, 43), (44, 45), (45, 46), (47, 46), (21, 43)]
faces = []
mesh = obj.data
mesh.from_pydata(verts, edges, faces)
mesh.update()
mesh.update()
return obj
else:
return None
def create_sample(obj):
# generated by rigify.utils.write_metarig
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
bones = {}
bone = arm.edit_bones.new('base')
bone.head[:] = 0.0000, 0.0000, 0.0000
bone.tail[:] = 0.0000, 0.0000, 0.3000
bone.roll = 0.0000
bone.use_connect = False
bones['base'] = bone.name
bone = arm.edit_bones.new('tentacle01')
bone.head[:] = 0.0000, 0.0000, 0.3000
bone.tail[:] = 0.0000, 0.0000, 0.4400
bone.roll = 0.0000
bone.use_connect = False
bone.parent = arm.edit_bones[bones['base']]
bones['tentacle01'] = bone.name
bone = arm.edit_bones.new('tentacle02')
bone.head[:] = 0.0000, 0.0000, 0.4400
bone.tail[:] = 0.0000, 0.0000, 0.5800
bone.roll = 0.0000
bone.use_connect = True
bone.parent = arm.edit_bones[bones['tentacle01']]
bones['tentacle02'] = bone.name
bone = arm.edit_bones.new('tentacle03')
bone.head[:] = 0.0000, 0.0000, 0.5800
bone.tail[:] = 0.0000, 0.0000, 0.7200
bone.roll = 0.0000
bone.use_connect = True
bone.parent = arm.edit_bones[bones['tentacle02']]
bones['tentacle03'] = bone.name
bone = arm.edit_bones.new('tentacle04')
bone.head[:] = 0.0000, 0.0000, 0.7200
bone.tail[:] = 0.0000, 0.0000, 0.8600
bone.roll = 0.0000
bone.use_connect = True
bone.parent = arm.edit_bones[bones['tentacle03']]
bones['tentacle04'] = bone.name
bone = arm.edit_bones.new('tentacle05')
bone.head[:] = 0.0000, 0.0000, 0.8600
bone.tail[:] = 0.0000, 0.0000, 1.0000
bone.roll = 0.0000
bone.use_connect = True
bone.parent = arm.edit_bones[bones['tentacle04']]
bones['tentacle05'] = bone.name
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones[bones['base']]
pbone.rigify_type = 'basic.super_copy'
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'
pbone = obj.pose.bones[bones['tentacle01']]
pbone.rigify_type = 'limbs.spline_tentacle'
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'
pbone = obj.pose.bones[bones['tentacle02']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone = obj.pose.bones[bones['tentacle03']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone = obj.pose.bones[bones['tentacle04']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = 'QUATERNION'
pbone = obj.pose.bones[bones['tentacle05']]
pbone.rigify_type = ''
pbone.lock_location = (False, False, False)
pbone.lock_rotation = (False, False, False)
pbone.lock_rotation_w = False
pbone.lock_scale = (False, False, False)
pbone.rotation_mode = '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