From 42f32d2ac0481cbbef2b5652c910ef15d8216448 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 20 Sep 2023 19:13:59 +0300 Subject: [PATCH] The spline tentacle rig was moved to the Rigify add-on. --- README.md | 36 - __init__.py | 6 + rigs/limbs/spline_tentacle.py | 1490 --------------------------------- 3 files changed, 6 insertions(+), 1526 deletions(-) delete mode 100644 rigs/limbs/spline_tentacle.py diff --git a/README.md b/README.md index 9437b60..7c9710e 100644 --- a/README.md +++ b/README.md @@ -58,42 +58,6 @@ original leg deform bones during animation. **Example file:** [demo-extra-heel.blend](https://www.dropbox.com/s/ll6ckj24yav6eyn/demo-extra-heel.blend?dl=0) demonstrates using this rig to implement a switch between low and high heel shoes via a property on the root bone. -### Spline IK Tentacle (`limbs.spline_tentacle`) - -This rig type implements a tentacle with an IK system using the Spline IK constraint. -The controls define control points of a Bezier curve, and the bone chain follows the curve. - -The curve control points are sorted into three groups: start, middle and end. The middle -controls are always visible and active, while the other two types can be shown and hidden -dynamically using properties; when enabled they appear next to the corresponding permanent -start/end control and can be moved from there. - -* **Extra Start Controls** specifies the number of optional start controls to generate. -* **Middle Controls** specifies the number of middle controls to generate. -* **Extra End Controls** specifies the number of optional end controls to generate. -* Curve Fit Mode: - + **Stretch To Fit** stretches the whole bone chain to fit the length of the curve defined - by the controls. - + **Direct Tip Control** turns the last bone of the chain into the end control, allowing - direct control over that bone, while the middle bones stretch to follow the curve and - cover the gap. This is similar to how regular IK works for limbs. - + **Manual Squash & Stretch** allows full manual control over the chain scaling, while the - chain covers as much of the curve as it can given its current length. -* **Radius Scaling** allows scaling the controls to control the thickness of the chain through the curve. -* **Maximum Radius** specifies the maximum scale allowed by the *Radius Scaling* feature. -* **FK Controls** generates an FK control chain and IK-FK snapping. - -**Runtime Options:** - -* **Start Controls** changes the number of visible optional start controls. -* **End Controls** changes the number of visible optional end controls. -* **End Twist Fix** (Direct Tip Control only) - For technical reasons, the rig can only determine the chain twist from the tip control - within the -180..180 degrees range. Exceeding that flips the twist direction. - This option allows working around the limitation by dialing in a rough estimate of - twist in full rotations, and letting the rig auto-correct to the precise value within - the 180 degrees range from the estimate. - ## Spine Rigs ### BlenRig-like Spine (`spines.blenrig_spine`) diff --git a/__init__.py b/__init__.py index 92be0cf..ebc76b4 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,12 @@ +# noinspection SpellCheckingInspection rigify_info = { "name": "Experimental Rigs by Alexander Gavrilov", + "author": "Alexander Gavrilov", + "description": + "Experimental and/or niche rigs made by a Rigify maintainer.\n" + "Includes a BlenRig-like spine, Body IK (knee & elbow IK), jiggles, skin transforms, etc.", "link": "https://github.com/angavrilov/angavrilov-rigs", + "blender": (4, 0, 0), } diff --git a/rigs/limbs/spline_tentacle.py b/rigs/limbs/spline_tentacle.py deleted file mode 100644 index 6535eee..0000000 --- a/rigs/limbs/spline_tentacle.py +++ /dev/null @@ -1,1490 +0,0 @@ - -import bpy - -import re -import itertools -import bisect -import math -import json - -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.rig_ui_template import PanelLayout, UTILITIES_FUNC_COMMON_IK_FK - -from rigify.rigs.chain_rigs import SimpleChainRig -from rigify.rigs.widgets import create_gear_widget - -from typing import NamedTuple, Sequence -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 - - # Create a spline object (replacing the old one if it exists) - self.spline_obj = self.generator.artifacts.create_new(self, 'CURVE', 'spline') - - # 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 - ) - - self.add_start_controls_buttons(panel, master, rig_name) - - 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 - ) - - self.add_end_controls_buttons(panel, master, rig_name) - - # End twist correction for directly controllable tip - if self.use_tip: - max_val = len(self.bones.org) * math.pi - - self.make_property( - master, 'end_twist', 0.0, min=-max_val, max=max_val, - subtype='ANGLE', # precision=0, step=1000.0, - description="Rough end twist estimate. The rig auto-corrects it to the actual tip orientation " - "within 180 degrees of the specified value" - ) - self.get_bone(master).id_properties_ui("end_twist").update(precision=0, step=1000.0) - - self.add_direct_tip_buttons(panel, master, rig_name) - - # IK/FK switch - if self.use_fk: - self.make_property(master, 'IK_FK', 0.0, description='IK/FK switch for '+rig_name) - - self.add_fk_snap_buttons(panel, master, rig_name) - - def add_start_controls_buttons(self, panel: 'PanelLayout', master: str, _rig_name: str): - row = panel.row(align=True) - row.custom_prop(master, 'start_controls', text="Start Controls") - - ctrl_bones = self.bones.ctrl.start - hook_bones = self.bones.mch.start_hooks - - add_toggle_control_button(row, prop_bone=master, prop_name='start_controls', - ctrl_bones=ctrl_bones, hook_bones=hook_bones, enable=True) - add_toggle_control_button(row, prop_bone=master, prop_name='start_controls', - ctrl_bones=ctrl_bones, hook_bones=hook_bones, enable=False) - - def add_end_controls_buttons(self, panel: 'PanelLayout', master: str, _rig_name: str): - row = panel.row(align=True) - row.custom_prop(master, 'end_controls', text="End Controls") - - ctrl_bones = self.bones.ctrl.end - hook_bones = self.bones.mch.end_hooks - - if self.use_tip: - ctrl_bones = ctrl_bones[1:] - hook_bones = hook_bones[1:] - - add_toggle_control_button(row, prop_bone=master, prop_name='end_controls', - ctrl_bones=ctrl_bones, hook_bones=hook_bones, enable=True) - add_toggle_control_button(row, prop_bone=master, prop_name='end_controls', - ctrl_bones=ctrl_bones, hook_bones=hook_bones, enable=False) - - # noinspection PyMethodMayBeStatic - def add_direct_tip_buttons(self, panel: 'PanelLayout', master: str, _rig_name: str): - panel.custom_prop(master, 'end_twist', text="End Twist Estimate") - - def add_fk_snap_buttons(self, panel: 'PanelLayout', master: str, rig_name: str): - 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 - ) - - add_spline_snap_ik_to_fk( - panel, - fk_bones=self.bones.ctrl.fk, ik_bones=self.get_ik_final(), - ik_ctrl_bones=ik_controls, - use_tip=self.use_tip, - use_stretch=self.use_stretch, - 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): - spline_data = self.spline_obj.data - 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) - - def make_spline_points(self, spline_data, all_controls): - spline = spline_data.splines.new('BEZIER') - - spline.bezier_points.add(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 - - 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' - scale_var = [driver_var_transform(self.obj, target, type='SCALE_AVG', space='LOCAL')] - - make_driver(key, 'value', expression=expr, variables=scale_var) - key.slider_min = 1 - self.max_curve_radius - - ############################## - # 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 - rot_fac = 1.0 / num_ik - if self.use_tip: - rot_var = [(self.bones.ctrl.master, 'end_twist')] - else: - 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, inherit_scale='ALIGNED') - - @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, inherit_scale='ALIGNED') - - @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) - - -########################### -# Limb IK to FK operator ## -########################### - -SCRIPT_REGISTER_OP_SNAP_IK_FK = ['POSE_OT_rigify_spline_tentacle_ik2fk'] - -SCRIPT_UTILITIES_OP_SNAP_IK_FK = UTILITIES_FUNC_COMMON_IK_FK + [''' -######################## -## Limb Snap IK to FK ## -######################## - -class RigifySplineTentacleIk2FkBase: - fk_bones: StringProperty(name="FK Bone Chain") - ik_bones: StringProperty(name="IK Result Bone Chain") - ctrl_bones: StringProperty(name="IK Controls") - use_tip: bpy.props.BoolProperty(name="Direct Tip Control") - use_stretch: bpy.props.BoolProperty(name="Manual Stretch") - - def init_execute(self, context): - self.fk_bone_list = json.loads(self.fk_bones) - self.ik_bone_list = json.loads(self.ik_bones) - self.ctrl_bone_list = json.loads(self.ctrl_bones) - if not self.use_tip: - self.twist_control_bone = self.ctrl_bone_list.pop() - - def save_frame_state(self, context, obj): - matrices = get_chain_transform_matrices(obj, self.fk_bone_list) - if not self.use_tip: - last_tail = matrices[-1].copy() - last_tail.translation = obj.pose.bones[self.fk_bone_list[-1]].tail - matrices.append(last_tail) - return matrices - - def apply_frame_state(self, context, obj, all_matrices): - ik_bones = [obj.pose.bones[k] for k in self.ik_bone_list] - ctrl_bones = [obj.pose.bones[k] for k in self.ctrl_bone_list] - - # Reset transformation of controls - for name in self.ctrl_bone_list + ([] if self.use_tip else [self.twist_control_bone]): - set_transform_from_matrix(obj, name, Matrix.Identity(4), space='LOCAL', keyflags=self.keyflags) - - # Position the first and last controls and update to ensure switchable parent is ready - set_transform_from_matrix( - obj, self.ctrl_bone_list[0], all_matrices[0], keyflags=self.keyflags, no_scale=True, no_rot=True) - - set_transform_from_matrix( - obj, self.ctrl_bone_list[-1], all_matrices[-1], keyflags=self.keyflags, no_scale=not self.use_tip) - - context.view_layer.update() - - # Find currently enabled controls - visible_ctrls = [ - bone for bone in ctrl_bones[1:-1] - if not (bone.bone.hide and obj.data.animation_data.drivers.find(bone.bone.path_from_id("hide"))) - ] - ctrl_count = len(visible_ctrls) + (0 if self.use_tip else 1) - max_pos = 1 - 0.25 / ctrl_count - ctrl_points = [ - (min((i+1) / ctrl_count, max_pos), ctrl) for i, ctrl in enumerate(visible_ctrls) - ] - - # Measure the fk polyline - points = [m.translation for m in all_matrices] - lengths = [(n - p).length for p, n in zip(points, points[1:])] - tot_length = sum(lengths) - - # Snap visible controls evenly to the polyline - total = 0 - - for seg_len, p, n in zip(lengths, points, points[1:]): - prev_total = total - total += seg_len / tot_length - while ctrl_points and ctrl_points[0][0] <= total: - fac, ctrl = ctrl_points.pop(0) - fac = (fac - prev_total) / (total - prev_total) - assert 0 <= fac <= 1 - pos = p * (1 - fac) + n * fac - set_transform_from_matrix( - obj, ctrl.name, Matrix.Translation(pos), keyflags=self.keyflags, no_scale=True, no_rot=True) - - # Initial approximation of twist and scale for the base control - context.view_layer.update() - - base_error_rotation = ik_bones[0].matrix.to_quaternion().inverted() @ all_matrices[0].to_quaternion() - base_twist = base_error_rotation.to_swing_twist('Y')[1] - base_scale = [b / a for a, b in zip(ik_bones[0].matrix.to_scale(), all_matrices[0].to_scale())] - - if self.use_stretch and any(con.type == 'MAINTAIN_VOLUME' and not con.mute - for con in ctrl_bones[0].constraints): - # Compensate for the maintain volume constraint - base_scale[0] *= pow(base_scale[1], 1.5) - base_scale[2] *= pow(base_scale[1], 1.5) - - if self.use_tip: - # Compensate for the effect of targeting the tip bone orientation - chain_len = len(all_matrices) - tip_factor = chain_len / (chain_len - 1) - base_twist = base_twist * tip_factor - base_scale = [pow(s, tip_factor) for s in base_scale] - - set_transform_from_matrix( - obj, self.ctrl_bone_list[0], Matrix.LocRotScale(None, Euler((0, base_twist, 0)), base_scale), - space='LOCAL', keyflags=self.keyflags, no_loc=True - ) - - # Approximation for the tip twist control, and correction for the base control - if not self.use_tip: - context.view_layer.update() - - tip_error_rotation = ik_bones[-1].matrix.to_quaternion().inverted() @ all_matrices[-1].to_quaternion() - tip_twist = tip_error_rotation.to_swing_twist('Y')[1] - tip_scale = [b / a for a, b in zip(ik_bones[-1].matrix.to_scale(), all_matrices[-1].to_scale())] - - if self.use_stretch: - # Compensate for the maintain volume constraint - tip_scale[0] *= pow(tip_scale[1], 1.5) - tip_scale[2] *= pow(tip_scale[1], 1.5) - else: - tip_scale = [1, 1, 1] - - # Compensate for the fraction removed from the base below - chain_len = len(all_matrices) - 1 - tip_factor = chain_len / (chain_len - 1) - new_tip_twist = tip_twist * tip_factor - new_tip_scale = [pow(s, tip_factor) for s in tip_scale] - - set_transform_from_matrix( - obj, self.twist_control_bone, Matrix.LocRotScale(None, Euler((0, new_tip_twist, 0)), new_tip_scale), - space='LOCAL', keyflags=self.keyflags, no_loc=True, no_scale=not self.use_stretch - ) - - # A fraction of the tip scale and twist is applied to the base bone too, so remove it - new_base_twist = base_twist - tip_twist / (chain_len - 1) - new_base_scale = [b / pow(t, 1 / (chain_len - 1)) for b, t in zip(base_scale, tip_scale)] - - set_transform_from_matrix( - obj, self.ctrl_bone_list[0], Matrix.LocRotScale(None, Euler((0, new_base_twist, 0)), new_base_scale), - space='LOCAL', keyflags=self.keyflags, no_loc=True - ) - -class POSE_OT_rigify_spline_tentacle_ik2fk(RigifySplineTentacleIk2FkBase, RigifySingleUpdateMixin, bpy.types.Operator): - bl_idname = "pose.rigify_spline_tentacle_ik2fk_" + rig_id - bl_label = "Snap IK->FK" - bl_description = "Approximately snap the IK chain to FK result. Note that this will never produce an exact match" -'''] - - -def add_spline_snap_ik_to_fk(panel: 'PanelLayout', *, - fk_bones: Sequence[str], - ik_bones: Sequence[str], - ik_ctrl_bones: Sequence[str], - use_tip: bool, use_stretch: bool, - rig_name=''): - panel.use_bake_settings() - panel.script.add_utilities(SCRIPT_UTILITIES_OP_SNAP_IK_FK) - panel.script.register_classes(SCRIPT_REGISTER_OP_SNAP_IK_FK) - - op_props = { - 'fk_bones': json.dumps(fk_bones), - 'ik_bones': json.dumps(ik_bones), - 'ctrl_bones': json.dumps(ik_ctrl_bones), - 'use_tip': use_tip, - 'use_stretch': use_stretch, - } - - panel.operator( - 'pose.rigify_spline_tentacle_ik2fk_{rig_id}', - text=f'IK->FK ({rig_name})', icon='SNAP_ON', properties=op_props) - - -SCRIPT_REGISTER_OP_TOGGLE_CONTROLS = ['POSE_OT_rigify_spline_tentacle_toggle_control'] - -SCRIPT_UTILITIES_OP_TOGGLE_CONTROLS = [''' -##################################### -## Toggle Spline Tentacle Controls ## -##################################### - -class RigifySplineTentacleToggleControlBase: - prop_bone: StringProperty(name="Settings Bone") - prop_name: StringProperty(name="Switch Property") - - ctrl_bones: StringProperty(name="Control Bones") - hook_bones: StringProperty(name="Hook Bones") - - @classmethod - def poll(cls, context): - return find_action(context.active_object) is not None - - def check_increment(self, obj, delta): - bone = obj.pose.bones[self.prop_bone] - ui_data = bone.id_properties_ui(self.prop_name).as_dict() - return ui_data["min"] <= bone[self.prop_name] + delta <= ui_data["max"] - - def get_property(self, obj): - return obj.pose.bones[self.prop_bone][self.prop_name] - - def set_property(self, obj, value): - obj.pose.bones[self.prop_bone][self.prop_name] = value - - def keyframe_increment(self, context, obj, delta): - action = find_action(obj) - bone = obj.pose.bones[self.prop_bone] - prop_quoted = rna_idprop_quote_path(self.prop_name) - - # Find the F-Curve - data_path = bone.path_from_id(prop_quoted) - fcurve = action.fcurves.find(data_path) or action.fcurves.new(data_path, action_group=self.prop_bone) - - # Ensure the current value is keyed at the start of the animation - keyflags = get_keying_flags(context) - frame = context.scene.frame_current - - if len(fcurve.keyframe_points) == 0: - min_x = min(fcu.keyframe_points[0].co[0] for fcu in action.fcurves if len(fcu.keyframe_points) > 0) - min_frame = nla_tweak_to_scene(obj.animation_data, min_x) - if min_frame < frame: - bone.keyframe_insert(prop_quoted, frame=min_frame, options=keyflags) - - # Keyframe the new value - cur_value = bone[self.prop_name] - new_value = cur_value + delta - - bone[self.prop_name] = new_value - bone.keyframe_insert(prop_quoted, frame=frame, options=keyflags) - - # Ensure constant interpolation - for key in fcurve.keyframe_points: - key.interpolation = 'CONSTANT' - - return fcurve, cur_value, new_value - - def get_hook_bone(self, obj, index): - hook_bones = json.loads(self.hook_bones) - return obj.pose.bones[hook_bones[index]] - - def get_control_bone(self, obj, index): - ctrl_bones = json.loads(self.ctrl_bones) - return obj.pose.bones[ctrl_bones[index]] - - def get_hook_position(self, obj, index): - hook = self.get_hook_bone(obj, index) - - hook_matrix_pose = hook.matrix.copy() - hook_matrix_local = obj.convert_space( - pose_bone=hook, matrix=hook_matrix_pose, from_space='POSE', to_space='LOCAL') - - return hook_matrix_pose.translation, hook_matrix_local.to_scale() - - def set_control_position(self, context, obj, index, location, scale): - ctrl = self.get_control_bone(obj, index) - - ctrl_matrix_local = obj.convert_space( - pose_bone=ctrl, matrix=Matrix.Translation(location), from_space='POSE', to_space='LOCAL') - - ctrl.location = ctrl_matrix_local.translation - ctrl.scale = scale - - keyframe_transform_properties(obj, ctrl.name, get_keying_flags(context), no_rot=True) - -class POSE_OT_rigify_spline_tentacle_toggle_control(RigifySplineTentacleToggleControlBase, bpy.types.Operator): - bl_idname = "pose.rigify_spline_tentacle_toggle_control_" + rig_id - bl_label = "Toggle And Keyframe Extra Control" - bl_options = {'UNDO', 'INTERNAL', 'REGISTER'} - - enable: bpy.props.BoolProperty(name="Enable a control") - - @classmethod - def description(cls, context, props): - return (("Enable" if props.enable else "Disable") + - " one more extra control in the middle of an animation, appropriately keyframing its position" - " and the count property to preserve animation continuity") - - def execute(self, context): - obj = context.active_object - delta = 1 if self.enable else -1 - - if not self.check_increment(obj, delta): - self.report({'ERROR'}, "There are no more controls to {'enable' if self.enable else 'disable'}") - return {'CANCELED'} - - # Find the control bone - index = self.get_property(obj) + min(0, delta) - ctrl = self.get_control_bone(obj, index) - - # Select the master control if hiding the active bone - select_bone = None - - if not self.enable and obj.data.bones.active == ctrl.bone: - select_bone = obj.pose.bones[self.prop_bone].bone - - # Capture the hook transform when the control is disabled, and keyframe toggle - if self.enable: - loc, scale = self.get_hook_position(obj, index) - - self.keyframe_increment(context, obj, 1) - else: - self.keyframe_increment(context, obj, -1) - - obj.update_tag(refresh={'DATA'}) - context.view_layer.update() - - loc, scale = self.get_hook_position(obj, index) - - # Keyframe the passive hook transform onto the control - self.set_control_position(context, obj, index, loc, scale) - - # Select the new control if enabling it - if self.enable: - select_bone = ctrl.bone - - if select_bone is not None: - for bone in obj.data.bones: - bone.select = bone == select_bone - obj.data.bones.active = select_bone - - obj.update_tag(refresh={'DATA'}) - return {'FINISHED'} -'''] - - -def add_toggle_control_button(panel: 'PanelLayout', *, - prop_bone: str, - prop_name: str, - ctrl_bones: Sequence[str], - hook_bones: Sequence[str], - enable=True, - text=''): - panel.use_bake_settings() - panel.script.add_utilities(SCRIPT_UTILITIES_OP_TOGGLE_CONTROLS) - panel.script.register_classes(SCRIPT_REGISTER_OP_TOGGLE_CONTROLS) - - op_props = { - 'prop_bone': prop_bone, - 'prop_name': prop_name, - 'ctrl_bones': json.dumps(ctrl_bones), - 'hook_bones': json.dumps(hook_bones), - 'enable': enable, - } - - row = panel.row(align=True) - - if enable: - row.enabled = row.expr_bone(prop_bone)[prop_name] < len(ctrl_bones) - else: - row.enabled = row.expr_bone(prop_bone)[prop_name] > 0 - - row.operator('pose.rigify_spline_tentacle_toggle_control_{rig_id}', text=text, - icon='ADD' if enable else 'REMOVE', - properties=op_props) - - -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 - if bcoll := arm.collections.active: - bcoll.assign(bone)