From d8046fb1cd2471d7e16da78a1680ce8d72fb55f3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 26 Jun 2018 17:23:02 +0300 Subject: [PATCH 1/2] Port the Pivot Slide feature of the legacy Rigify spine to super_spine. Pivot Slide seems to be the largest feature that was lost when merging original and PitchiPoy rigs to form the new rig set. This ports it as an option in the new spine rig, thus making the legacy spine completely obsolete. When not enabled, the generated rig shouldn't change. Also, head/tail and tweaks should work the same even with the dynamic pivot mode. The code is directly based on the legacy implementation, but extensively edited to make it fit into super_spine code. --- rigs/spines/super_spine.py | 743 +++++++++++++++++++++++++++++-------- 1 file changed, 592 insertions(+), 151 deletions(-) diff --git a/rigs/spines/super_spine.py b/rigs/spines/super_spine.py index c96af46..d17d0c5 100644 --- a/rigs/spines/super_spine.py +++ b/rigs/spines/super_spine.py @@ -1,6 +1,6 @@ import bpy from mathutils import Vector -from ...utils import copy_bone, flip_bone, put_bone, org, align_bone_y_axis, align_bone_x_axis +from ...utils import copy_bone, new_bone, flip_bone, put_bone, org, align_bone_y_axis, align_bone_x_axis from ...utils import strip_org, make_deformer_name, connected_children_names from ...utils import create_circle_widget, create_sphere_widget, create_neck_bend_widget, create_neck_tweak_widget from ..widgets import create_ballsocket_widget @@ -12,15 +12,13 @@ torso = '%s' if is_selected( controls ): - if hasattr(pose_bones[torso],'["%s"]'): - layout.prop( pose_bones[ torso ], '["%s"]', slider = True ) - if hasattr(pose_bones[torso],'["%s"]'): - layout.prop( pose_bones[ torso ], '["%s"]', slider = True ) +""" + +script_attr = """ if hasattr(pose_bones[torso],'["%s"]'): layout.prop( pose_bones[ torso ], '["%s"]', slider = True ) """ - class Rig: def __init__(self, obj, bone_name, params): @@ -35,28 +33,80 @@ def __init__(self, obj, bone_name, params): self.copy_rotation_axes = params.copy_rotation_axes self.use_head = params.use_head self.use_tail = params.use_tail + self.use_pivot_slide = params.use_pivot_slide - # Check if user provided the pivot position - if params.pivot_pos: - self.pivot_pos = params.pivot_pos + 1 - else: - raise MetarigError( - "RIGIFY ERROR: please specify pivot bone position" - ) + if self.use_pivot_slide: + # Check tail size + if self.use_tail and params.tail_pos > 1: + self.tail_pos = params.tail_pos + else: + self.tail_pos = 0 + + self.pivot_pos = self.tail_pos + 1 + + # Check neck + head size + if self.use_head: + if params.neck_pos < self.pivot_pos + 3: + raise MetarigError( + "RIGIFY ERROR: bad neck position: spine is too short" + ) + elif params.neck_pos > len(self.org_bones): + raise MetarigError( + "RIGIFY ERROR: bad neck position: exceeds bone count" + ) + else: + self.neck_pos = params.neck_pos + else: + self.neck_pos = len(self.org_bones) + 1 + + # Parse control bone indices + self.torso_size = self.neck_pos - self.pivot_pos + self.control_indices = [0, self.torso_size - 1] + + temp = self.params.spine_control_list.split(",") + for i in temp: + try: + j = int(i) - self.pivot_pos + except ValueError: + pass + else: + if (j > 0) and (j < self.torso_size) and (j not in self.control_indices): + self.control_indices += [j] + + self.control_indices.sort() + + # Clamp pivot_rest to within the middle bones of the spine + self.pivot_rest = self.params.rest_pivot_slide + self.pivot_rest = max(self.pivot_rest, 1.0 / self.torso_size) + self.pivot_rest = min(self.pivot_rest, 1.0 - (1.0 / self.torso_size)) - # Check if neck is lower than pivot - if self.use_head and params.neck_pos <= params.pivot_pos and params.neck_pos != 0: - raise MetarigError( - "RIGIFY ERROR: Neck cannot be below or the same as pivot. (use 0 for no neck)" - ) else: - self.neck_pos = params.neck_pos + # Check if user provided the pivot position + if params.pivot_pos: + self.pivot_pos = params.pivot_pos + 1 + else: + raise MetarigError( + "RIGIFY ERROR: please specify pivot bone position" + ) + + # Check if neck is lower than pivot + if self.use_head and params.neck_pos <= params.pivot_pos and params.neck_pos != 0: + raise MetarigError( + "RIGIFY ERROR: Neck cannot be below or the same as pivot. (use 0 for no neck)" + ) + else: + self.neck_pos = params.neck_pos - if not self.use_head: - self.neck_pos = len(self.org_bones) + if not self.use_head: + self.neck_pos = len(self.org_bones) - if self.use_tail and self.pivot_pos - 2 > 0: - self.tail_pos = params.tail_pos + if self.use_tail and params.tail_pos > 1: + if params.tail_pos >= params.pivot_pos: + raise MetarigError( + "RIGIFY ERROR: Tail cannot be above or the same as pivot." + ) + + self.tail_pos = params.tail_pos # Assign values to tweak layers props if opted by user if params.tweak_extra_layers: @@ -430,6 +480,191 @@ def create_tail(self, tail_bones): 'original_names': tail_bones } + def create_pivot_slide_torso(self, torso_bones): + bpy.ops.object.mode_set(mode='EDIT') + eb = self.obj.data.edit_bones + + #------------------------- + # Get rest slide position + i, a = divmod(self.pivot_rest * len(torso_bones), 1) + if i >= self.torso_size: + i, a = i-1, 1.0 + + pivot_e = eb[org(torso_bones[int(i)])] + pivot_rest_pos = pivot_e.head.copy() + pivot_e.vector * a + + #---------------------- + # Create controls and their mechanisms + + controls = [] + subcontrols = [] + control_parents = [] + + main_control = new_bone(self.obj, 'torso') + put_bone(self.obj, main_control, pivot_rest_pos) + eb[main_control].length = self.spine_length / 2 + + eb[main_control].layers = eb[self.org_bones[0]].layers + + for i in self.control_indices: + name = copy_bone(self.obj, org(torso_bones[i]), torso_bones[i]) + put_bone(self.obj, name, pivot_rest_pos) + + subname = new_bone(self.obj, make_mechanism_name("sub_" + torso_bones[i])) + put_bone(self.obj, subname, pivot_rest_pos) + eb[subname].length = eb[name].length / 3 + + controls.append(name) + subcontrols.append(subname) + + for i, name in zip(self.control_indices[1:-1], controls[1:-1]): + par_name = new_bone(self.obj, make_mechanism_name("par_" + torso_bones[i])) + put_bone(self.obj, par_name, pivot_rest_pos) + eb[par_name].length = eb[name].length / 2 + + control_parents.append(par_name) + + #------------------------- + # Create mch and tweaks + + twk, mch = [], [] + + for b in torso_bones: + mch_name = copy_bone(self.obj, org(b), make_mechanism_name(b)) + twk_name = copy_bone(self.obj, org(b), "tweak_" + b) + eb[twk_name].length /= 2 + + mch.append(mch_name) + twk.append(twk_name) + + #------------------------- + # Create flex and reverse spine chains + + flex_bones = [] + flex_subs = [] + rev_bones = [] + + prev_rev = None + + for b in torso_bones: + bone = copy_bone(self.obj, org(b), make_mechanism_name(b + ".flex")) + + sub = new_bone(self.obj, make_mechanism_name(b + ".flex_s")) + put_bone(self.obj, sub, eb[bone].head) + eb[sub].length = eb[bone].length / 4 + + rev = copy_bone(self.obj, org(b), make_mechanism_name(b + ".reverse")) + flip_bone(self.obj, rev) + eb[rev].tail = Vector(eb[bone].head) + put_bone(self.obj, rev, eb[prev_rev].tail if prev_rev else pivot_rest_pos) + + flex_bones.append(bone) + flex_subs.append(sub) + rev_bones.append(rev) + prev_rev = rev + + return [{ + 'ctrl': main_control, + },{ + 'mch' : mch, + 'tweak' : twk, + 'controls' : controls, + 'subcontrols': subcontrols, + 'control_parents': control_parents, + 'flex_bones': flex_bones, + 'flex_subs': flex_subs, + 'rev_bones': rev_bones, + 'torso_bones': torso_bones + }] + + def parent_pivot_slide_torso_bones(self, bones): + eb = self.obj.data.edit_bones + data = bones['chest'] + + # Parent controls + main_control = bones['pivot']['ctrl'] + + eb[main_control].parent = eb[self.org_bones[0]].parent + + controls = data['controls'] + + for name, subname in zip(controls, data['subcontrols']): + eb[name].parent = eb[main_control] + eb[subname].parent = eb[name] + + for name, par_name in zip(controls[1:-1], data['control_parents']): + eb[par_name].parent = eb[main_control] + eb[name].parent = eb[par_name] + + # Parent flex and reverse chains + prev_bone = None + + for bone, sub, rev in zip(data['flex_bones'], data['flex_subs'], data['rev_bones']): + if prev_bone: + eb[sub].parent = eb[prev_bone] + eb[sub].use_connect = True + else: + eb[sub].parent = eb[controls[0]] + + eb[bone].parent = eb[sub] + eb[rev].parent = eb[bone] + prev_bone = bone + + # Parent mch and tweaks + prev_bone = None + + for mch, twk in zip(data['mch'], data['tweak']): + if prev_bone: + eb[mch].parent = eb[prev_bone] + eb[mch].use_connect = True + else: + eb[mch].parent = eb[self.org_bones[0]].parent + + eb[twk].parent = eb[mch] + prev_bone = mch + + def parent_torso_bones(self, bones): + eb = self.obj.data.edit_bones + + # Parent hips and chest controls to torso + eb[bones['chest']['ctrl']].parent = eb[bones['pivot']['ctrl']] + eb[bones['hips']['ctrl']].parent = eb[bones['pivot']['ctrl']] + + # Chest mch bones and neck mch + for i, b in enumerate(bones['chest']['mch']): + if i == 0: + eb[b].parent = eb[bones['pivot']['ctrl']] + elif b: + eb[b].parent = eb[bones['chest']['mch'][i-1]] + + # Hips mch bones + for i, b in enumerate(bones['hips']['mch']): + if i == len(bones['hips']['mch']) - 1: + eb[b].parent = eb[bones['pivot']['ctrl']] + else: + eb[b].parent = eb[bones['hips']['mch'][i+1]] + + # mch pivot + eb[bones['pivot']['mch']].parent = eb[bones['chest']['mch'][0]] + + # MCH widgets + eb[bones['chest']['mch_wgt']].parent = eb[bones['chest']['mch'][-1]] + eb[bones['hips']['mch_wgt']].parent = eb[bones['hips']['mch'][0]] + + # Chest tweaks + for twk, mch in zip( bones['chest']['tweak'], bones['chest']['mch']): + if bones['chest']['tweak'].index(twk) == 0: + eb[twk].parent = eb[bones['pivot']['mch']] + else: + eb[twk].parent = eb[mch] + + # Hips tweaks + for i, twk in enumerate(bones['hips']['tweak']): + if i == 0: + eb[twk].parent = eb[bones['hips']['mch'][i]] + else: + eb[twk].parent = eb[bones['hips']['mch'][i-1]] + def parent_bones(self, bones): org_bones = self.org_bones bpy.ops.object.mode_set(mode='EDIT') @@ -441,6 +676,15 @@ def parent_bones(self, bones): eb[b].parent = eb[bones['def'][i-1]] # to previous eb[b].use_connect = True + if self.use_pivot_slide: + self.parent_pivot_slide_torso_bones(bones) + + torso_tweaks = bones['chest']['tweak'] + else: + self.parent_torso_bones(bones) + + torso_tweaks = bones['hips']['tweak'] + bones['chest']['tweak'] + # Parent control bones # Head control => MCH-rotation_head if self.use_head: @@ -466,10 +710,6 @@ def parent_bones(self, bones): # eb[bones['neck']['neck_bend']].parent = eb[bones['neck']['ctrl_neck']] eb[bones['neck']['neck_bend']].parent = eb[bones['neck']['mch_str']] - # Parent hips and chest controls to torso - eb[bones['chest']['ctrl']].parent = eb[bones['pivot']['ctrl']] - eb[bones['hips']['ctrl']].parent = eb[bones['pivot']['ctrl']] - # Parent mch bones if bones['neck']['ctrl_neck']: # Neck mch @@ -483,27 +723,8 @@ def parent_bones(self, bones): # if org_b in b.name: # b.parent = eb[org(org_b)] - # Chest mch bones and neck mch - chest_mch = bones['chest']['mch'] + [bones['neck']['mch_neck']] - for i, b in enumerate(chest_mch): - if i == 0: - eb[b].parent = eb[bones['pivot']['ctrl']] - elif b: - eb[b].parent = eb[chest_mch[i-1]] - - # Hips mch bones - for i, b in enumerate(bones['hips']['mch']): - if i == len(bones['hips']['mch']) - 1: - eb[b].parent = eb[bones['pivot']['ctrl']] - else: - eb[b].parent = eb[bones['hips']['mch'][i+1]] - - # mch pivot - eb[bones['pivot']['mch']].parent = eb[bones['chest']['mch'][0]] - - # MCH widgets - eb[bones['chest']['mch_wgt']].parent = eb[bones['chest']['mch'][-1]] - eb[bones['hips']['mch_wgt']].parent = eb[bones['hips']['mch'][0]] + if bones['neck']['mch_neck']: + eb[bones['neck']['mch_neck']].parent = eb[bones['chest']['mch'][-1]] # Neck Tweaks if bones['neck']['tweak']: @@ -514,24 +735,10 @@ def parent_bones(self, bones): else: eb[twk].parent = eb[ bones['neck']['mch'][i-1]] - # Chest tweaks - for twk, mch in zip( bones['chest']['tweak'], bones['chest']['mch']): - if bones['chest']['tweak'].index(twk) == 0: - eb[twk].parent = eb[bones['pivot']['mch']] - else: - eb[twk].parent = eb[mch] - - # Hips tweaks - for i, twk in enumerate(bones['hips']['tweak']): - if i == 0: - eb[twk].parent = eb[bones['hips']['mch'][i]] - else: - eb[twk].parent = eb[bones['hips']['mch'][i-1]] - # Tail mchs if self.use_tail: mch_rot_tail = bones['tail']['mch_tail'] - eb[mch_rot_tail].parent = eb[bones['hips']['tweak'][0]] + eb[mch_rot_tail].parent = eb[torso_tweaks[0]] # Tail tweaks if self.use_tail: @@ -546,7 +753,8 @@ def parent_bones(self, bones): if self.use_tail: tweaks += bones['tail']['tweak'] - tweaks += bones['hips']['tweak'] + bones['chest']['tweak'] + tweaks += torso_tweaks + if self.use_head: tweaks += bones['neck']['tweak'] + [bones['neck']['ctrl']] @@ -576,8 +784,205 @@ def make_constraint(self, bone, constraint): for p in [k for k in constraint.keys() if k in dir(const)]: setattr(const, p, constraint[p]) + return const + + + def constrain_pivot_slide_torso_bones(self, bones): + pb = self.obj.pose.bones + data = bones['chest'] + + #----------------------------------------- + # Intermediate controls follow hips and shoulders + + main_control = bones['pivot']['ctrl'] + + controls = data['controls'] + subcontrols = data['subcontrols'] + control_parents = data['control_parents'] + torso_bones = data['torso_bones'] + + for name, par_name, i in zip(controls[1:-1], control_parents, self.control_indices[1:-1]): + prop = rna_idprop_ui_prop_get(pb[name], "bend_alpha", create=True) + pb[name]["bend_alpha"] = i / (len(torso_bones) - 1) + prop["min"] = prop["soft_min"] = 0.0 + prop["max"] = prop["soft_max"] = 1.0 + + prop = rna_idprop_ui_prop_get(pb[name], "auto_rotate", create=True) + pb[name]["auto_rotate"] = 1.0 + prop["min"] = prop["soft_min"] = 0.0 + prop["max"] = prop["soft_max"] = 1.0 + + # hips + con1 = self.make_constraint(par_name, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': subcontrols[0], + }); + + driver = con1.driver_add('influence').driver + driver.type = 'AVERAGE' + + var = driver.variables.new() + var.name = "auto" + var.targets[0].id = self.obj + var.targets[0].data_path = pb[name].path_from_id() + '["auto_rotate"]' + + # shoulders + con2 = self.make_constraint(par_name, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': subcontrols[-1], + }); + + driver = con2.driver_add("influence").driver + driver.type = 'SCRIPTED' + driver.expression = "alpha * auto" + + var = driver.variables.new() + var.name = "alpha" + var.targets[0].id = self.obj + var.targets[0].data_path = pb[name].path_from_id() + '["bend_alpha"]' + + var = driver.variables.new() + var.name = "auto" + var.targets[0].id = self.obj + var.targets[0].data_path = pb[name].path_from_id() + '["auto_rotate"]' + + #---------------------------------- + # Constrain flex spine to controls + + flex_subs = data['flex_subs'] + + for i, name in zip(self.control_indices, subcontrols): + self.make_constraint(flex_subs[i], { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': name, + }) + + for i, j, name1, name2 in zip(self.control_indices, self.control_indices[1:], subcontrols, subcontrols[1:]): + for n in range(i + 1, j): + bone = flex_subs[n] + alpha = (n - i) / (j - i) + + self.make_constraint(bone, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': name1, + }) + + self.make_constraint(bone, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': name2, + # original rigify spine uses custom property + 'influence': alpha, + }) + + #---------------------------------------- + # Link reverse spine + + rev_bones = data['rev_bones'] + prev_bone = None + + for bone in rev_bones: + if prev_bone: + self.make_constraint(bone, { + 'constraint': 'COPY_LOCATION', + 'subtarget': prev_bone, + 'head_tail': 1.0, + }); + else: + self.make_constraint(bone, { + 'constraint': 'COPY_LOCATION', + 'subtarget': main_control, + }); + + prev_bone = bone + + #---------------------------------------- + # Constrain mch bones to flex spine + + flex_bones = data['flex_bones'] + + for mch, fbone in zip(data['mch'], flex_bones): + self.make_constraint(mch, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': fbone, + }); + + #--------------------------- + # Create pivot slide system + + base_bone = data['mch'][0] + main_control_p = pb[main_control] + + prop = rna_idprop_ui_prop_get(main_control_p, "pivot_slide", create=True) + main_control_p["pivot_slide"] = self.pivot_rest + prop["min"] = 0.0 + prop["max"] = 1.0 + prop["soft_min"] = 1.0 / self.torso_size + prop["soft_max"] = 1.0 - (1.0 / self.torso_size) + + pivot_slide_id = main_control_p.path_from_id() + '["pivot_slide"]' + + self.make_constraint(base_bone, { + 'constraint': 'COPY_LOCATION', + 'subtarget': rev_bones[0], + }); + + for i, rb in enumerate(rev_bones): + con = self.make_constraint(base_bone, { + 'constraint': 'COPY_LOCATION', + 'name': "slide." + str(i+1), + 'subtarget': rb, + 'head_tail': 1.0, + }); + + fcurve = con.driver_add("influence") + driver = fcurve.driver + driver.type = 'AVERAGE' + + var = driver.variables.new() + var.name = "slide" + var.targets[0].id = self.obj + var.targets[0].data_path = pivot_slide_id + + mod = fcurve.modifiers[0] + mod.poly_order = 1 + mod.coefficients[0] = -i + mod.coefficients[1] = len(rev_bones) + + def constrain_torso_bones(self, bones): + # Intermediary mch bones + intermediaries = [bones['chest'], bones['hips']] + + for l in intermediaries: + mch = l['mch'] + + for j, b in enumerate(mch): + factor = float(1 / len(l['tweak'])) + self.make_constraint(b, { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': l['ctrl'], + 'influence': factor, + 'owner_space': 'LOCAL', + 'target_space': 'LOCAL' + }) + + # MCH pivot + self.make_constraint(bones['pivot']['mch'], { + 'constraint': 'COPY_TRANSFORMS', + 'subtarget': bones['hips']['mch'][-1], + 'owner_space': 'LOCAL', + 'target_space': 'LOCAL' + }) + def constrain_bones(self, bones): # MCH bones + if self.use_pivot_slide: + self.constrain_pivot_slide_torso_bones(bones) + + torso_tweaks = bones['chest']['tweak'] + else: + self.constrain_torso_bones(bones) + + torso_tweaks = bones['hips']['tweak'] + bones['chest']['tweak'] # head and neck MCH bones for b in [bones['neck']['mch_head'], bones['neck']['mch_neck']]: @@ -602,59 +1007,44 @@ def constrain_bones(self, bones): 'subtarget': bones['neck']['ctrl'], }) - # Intermediary mch bones - intermediaries = [bones['neck'], bones['chest'], bones['hips']] - - for i, l in enumerate(intermediaries): - mch = l['mch'] + # Neck mch-s + l = bones['neck'] + mch = l['mch'] + for j, b in enumerate(mch): + if len(bones['neck']['original_names']) > 3: + self.make_constraint(b, { + 'constraint': 'COPY_LOCATION', + 'subtarget': org(l['original_names'][j+1]), + 'influence': 1.0 + }) + else: + nfactor = float((j + 1) / len(mch)) + self.make_constraint(b, { + 'constraint': 'COPY_ROTATION', + 'subtarget': l['ctrl'], + 'influence': nfactor + }) - for j, b in enumerate(mch): + step = 2/(len(mch)+1) + xval = (j+1)*step + influence = 2*xval - xval**2 #parabolic influence of pivot - if i == 0: # Neck mch-s - if len(bones['neck']['original_names']) > 3: - self.make_constraint(b, { - 'constraint': 'COPY_LOCATION', - 'subtarget': org(l['original_names'][j+1]), - 'influence': 1.0 - }) - else: - nfactor = float((j + 1) / len(mch)) - self.make_constraint(b, { - 'constraint': 'COPY_ROTATION', - 'subtarget': l['ctrl'], - 'influence': nfactor - }) - - step = 2/(len(mch)+1) - xval = (j+1)*step - influence = 2*xval - xval**2 #parabolic influence of pivot - - if bones['neck']['neck_bend']: - self.make_constraint(b, { - 'constraint': 'COPY_LOCATION', - 'subtarget': l['neck_bend'], - 'influence': influence, - 'use_offset': True, - 'owner_space': 'LOCAL', - 'target_space': 'LOCAL' - }) - - if len(bones['neck']['original_names']) > 3: - self.make_constraint(b, { - 'constraint': 'COPY_SCALE', - 'subtarget': org(l['original_names'][j+1]), - 'influence': 1.0 - }) + if bones['neck']['neck_bend']: + self.make_constraint(b, { + 'constraint': 'COPY_LOCATION', + 'subtarget': l['neck_bend'], + 'influence': influence, + 'use_offset': True, + 'owner_space': 'LOCAL', + 'target_space': 'LOCAL' + }) - else: - factor = float(1 / len(l['tweak'])) - self.make_constraint(b, { - 'constraint': 'COPY_TRANSFORMS', - 'subtarget': l['ctrl'], - 'influence': factor, - 'owner_space': 'LOCAL', - 'target_space': 'LOCAL' - }) + if len(bones['neck']['original_names']) > 3: + self.make_constraint(b, { + 'constraint': 'COPY_SCALE', + 'subtarget': org(l['original_names'][j+1]), + 'influence': 1.0 + }) # Tail ctrls if self.use_tail: @@ -681,21 +1071,14 @@ def constrain_bones(self, bones): 'influence': 1.0, }) - # MCH pivot - self.make_constraint(bones['pivot']['mch'], { - 'constraint': 'COPY_TRANSFORMS', - 'subtarget': bones['hips']['mch'][-1], - 'owner_space': 'LOCAL', - 'target_space': 'LOCAL' - }) - # DEF bones deform = bones['def'] tweaks = [] if self.use_tail: tweaks += bones['tail']['tweak'] - tweaks += bones['hips']['tweak'] + bones['chest']['tweak'] + tweaks += torso_tweaks + if self.use_head: tweaks += bones['neck']['tweak'] + [bones['neck']['ctrl']] @@ -815,7 +1198,9 @@ def locks_and_widgets(self, bones): # Locks tweaks = bones['neck']['tweak'] + bones['chest']['tweak'] - tweaks += bones['hips']['tweak'] + + if not self.use_pivot_slide: + tweaks += bones['hips']['tweak'] if self.use_tail: tweaks += bones['tail']['tweak'] @@ -837,10 +1222,27 @@ def locks_and_widgets(self, bones): ) # Assigning widgets to control bones - gen_ctrls = [ - bones['chest']['ctrl'], - bones['hips']['ctrl'] - ] + gen_ctrls = [] + chest = '' + + if self.use_pivot_slide: + mch_bones = bones['chest']['mch'] + + for name, i in zip(bones['chest']['controls'], self.control_indices): + pb[name].custom_shape_transform = pb[mch_bones[i]] + + create_circle_widget( + self.obj, name, + radius=1.0, head_tail=0.5, with_line=False, + bone_transform_name=mch_bones[i] + ) + + else: + chest = bones['chest']['ctrl'] + gen_ctrls += [ + bones['chest']['ctrl'], + bones['hips']['ctrl'] + ] tail_ctrls = [] if self.use_tail and bones['tail']['ctrl']: @@ -862,7 +1264,7 @@ def locks_and_widgets(self, bones): radius = 1.0 # place chest on neck-base for very long necks - if bone == bones['chest']['ctrl'] and len(bones['neck']['original_names']) > 3: + if bone == chest and len(bones['neck']['original_names']) > 3: head_tail = 0.0 else: head_tail = 0.75 @@ -921,17 +1323,20 @@ def locks_and_widgets(self, bones): ) # place widgets on correct bones - chest_widget_loc = pb[bones['chest']['mch_wgt']] - pb[bones['chest']['ctrl']].custom_shape_transform = chest_widget_loc + if not self.use_pivot_slide: + chest_widget_loc = pb[bones['chest']['mch_wgt']] + + pb[bones['chest']['ctrl']].custom_shape_transform = chest_widget_loc - hips_widget_loc = pb[bones['hips']['mch_wgt']] + hips_widget_loc = pb[bones['hips']['mch_wgt']] + if self.use_tail: + hips_widget_loc = pb[bones['def'][self.tail_pos]] + + pb[bones['hips']['ctrl']].custom_shape_transform = hips_widget_loc if self.use_tail: - hips_widget_loc = pb[bones['def'][self.tail_pos]] pb[bones['tail']['ctrl_tail']].custom_shape_transform = pb[bones['tail']['tweak'][0]] - pb[bones['hips']['ctrl']].custom_shape_transform = hips_widget_loc - # Assigning widgets to tweak bones and layers for bone in tweaks: @@ -972,10 +1377,16 @@ def generate(self): bones = {} bones['def'] = self.create_deform() # Gets org bones from self - bones['pivot'] = self.create_pivot(self.pivot_pos) + + if self.use_pivot_slide: + info = self.create_pivot_slide_torso(upper_torso_bones) + bones['pivot'], bones['chest'] = info + else: + bones['pivot'] = self.create_pivot(self.pivot_pos) + bones['chest'] = self.create_chest(upper_torso_bones) + bones['hips'] = self.create_hips(lower_torso_bones) + bones['neck'] = self.create_neck(neck_bones) - bones['chest'] = self.create_chest(upper_torso_bones) - bones['hips'] = self.create_hips(lower_torso_bones) # TODO: Add create tail if tail_bones: @@ -994,7 +1405,10 @@ def generate(self): return controls = [bones['neck']['ctrl'], bones['neck']['ctrl_neck']] - controls += [bones['chest']['ctrl'], bones['hips']['ctrl']] + if self.use_pivot_slide: + controls += bones['chest']['controls'] + else: + controls += [bones['chest']['ctrl'], bones['hips']['ctrl']] controls += [bones['pivot']['ctrl']] if self.use_tail: @@ -1002,16 +1416,17 @@ def generate(self): # Create UI controls_string = ", ".join(["'" + x + "'" for x in controls]) - return [script % ( - controls_string, - bones['pivot']['ctrl'], - 'head_follow', - 'head_follow', - 'neck_follow', - 'neck_follow', - 'tail_follow', - 'tail_follow', - )] + script_str = script % (controls_string, bones['pivot']['ctrl']) + + script_str += script_attr % ('head_follow', 'head_follow') + if self.use_head: + script_str += script_attr % ('neck_follow', 'neck_follow') + if self.use_tail: + script_str += script_attr % ('tail_follow', 'tail_follow') + if self.use_pivot_slide: + script_str += script_attr % ('pivot_slide', 'pivot_slide') + + return [script_str] def add_parameters(params): @@ -1051,12 +1466,30 @@ def add_parameters(params): description='Create tail bones' ) + params.use_pivot_slide = bpy.props.BoolProperty( + name='use_pivot_slide', + default=False, + description='Allow changing the pivot position dynamically' + ) + params.use_head = bpy.props.BoolProperty( name='use_head', default=True, description='Create head and neck bones' ) + # Pivot Slide mode parameters + params.rest_pivot_slide = bpy.props.FloatProperty( + name="rest_pivot_slide", + default=0.25, min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, + description="The pivot slide value in the rest pose" + ) + + params.spine_control_list = bpy.props.StringProperty( + name="Extra controls", default="", + description="Comma separated list of indexes that should get additional spine bend controls" + ) + # Setting up extra layers for the FK and tweak params.tweak_extra_layers = bpy.props.BoolProperty( name="tweak_extra_layers", @@ -1076,14 +1509,22 @@ def parameters_ui(layout, params): r = layout.row(align=True) r.prop(params, "use_head", toggle=True, text="Head") + r.prop(params, "use_pivot_slide", toggle=True, text="Pivot Slide") r.prop(params, "use_tail", toggle=True, text="Tail") r = layout.row() r.prop(params, "neck_pos") r.enabled = params.use_head - r = layout.row() - r.prop(params, "pivot_pos") + if params.use_pivot_slide: + r = layout.row() + r.prop(params, "rest_pivot_slide") + + r = layout.row() + r.prop(params, "spine_control_list") + else: + r = layout.row() + r.prop(params, "pivot_pos") r = layout.row() r.prop(params, "tail_pos") From 1bc8300dacf694f93c04596da551d0a32085857f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 27 Jun 2018 11:48:16 +0300 Subject: [PATCH 2/2] Make ORG bones in the tail and spine match the DEF bone positions. The way they were just dangling off tweak bones just made no sense. If the neck is long, ORG bones are used for IK, so it's unchanged. --- rigs/spines/super_spine.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/rigs/spines/super_spine.py b/rigs/spines/super_spine.py index d17d0c5..c12f875 100644 --- a/rigs/spines/super_spine.py +++ b/rigs/spines/super_spine.py @@ -696,7 +696,7 @@ def parent_bones(self, bones): for i, b in enumerate(tail_ctrl[:-1]): eb[b].parent = eb[tail_ctrl[i+1]] eb[tail_ctrl[-1]].parent = eb[bones['tail']['mch_tail']] - eb[bones['tail']['ctrl_tail']].parent = eb[org_bones[self.tail_pos]] + eb[bones['tail']['ctrl_tail']].parent = eb[torso_tweaks[0]] if bones['neck']['ctrl_neck']: # MCH stretch => neck ctrl @@ -770,6 +770,8 @@ def parent_bones(self, bones): if self.use_head: eb[org_bones[-1]].parent = eb[bones['neck']['ctrl']] + else: + eb[org_bones[-1]].parent = eb[tweaks[-1]] def make_constraint(self, bone, constraint): bpy.ops.object.mode_set(mode='OBJECT') @@ -1010,8 +1012,11 @@ def constrain_bones(self, bones): # Neck mch-s l = bones['neck'] mch = l['mch'] + + has_long_neck = len(bones['neck']['original_names']) > 3 + for j, b in enumerate(mch): - if len(bones['neck']['original_names']) > 3: + if has_long_neck: self.make_constraint(b, { 'constraint': 'COPY_LOCATION', 'subtarget': org(l['original_names'][j+1]), @@ -1039,7 +1044,7 @@ def constrain_bones(self, bones): 'target_space': 'LOCAL' }) - if len(bones['neck']['original_names']) > 3: + if has_long_neck: self.make_constraint(b, { 'constraint': 'COPY_SCALE', 'subtarget': org(l['original_names'][j+1]), @@ -1078,32 +1083,36 @@ def constrain_bones(self, bones): tweaks += bones['tail']['tweak'] tweaks += torso_tweaks + non_head_tweaks = len(tweaks) if self.use_head: tweaks += bones['neck']['tweak'] + [bones['neck']['ctrl']] - for d, t in zip(deform, tweaks): - tidx = tweaks.index(t) + for tidx, (d, t, o) in enumerate(zip(deform, tweaks, self.org_bones)): + if tidx < non_head_tweaks or not has_long_neck: + copy_tgt, track_bone = o, o + else: + copy_tgt, track_bone = t, d self.make_constraint(d, { 'constraint': 'COPY_TRANSFORMS', - 'subtarget': t + 'subtarget': copy_tgt, }) if tidx != len(tweaks) - 1: if self.use_tail and t in bones['tail']['tweak']: - self.make_constraint(d, { + self.make_constraint(track_bone, { 'constraint': 'DAMPED_TRACK', 'subtarget': tweaks[tidx + 1], 'track_axis': 'TRACK_NEGATIVE_Y' }) else: - self.make_constraint(d, { + self.make_constraint(track_bone, { 'constraint': 'DAMPED_TRACK', 'subtarget': tweaks[tidx + 1], }) - self.make_constraint(d, { + self.make_constraint(track_bone, { 'constraint': 'STRETCH_TO', 'subtarget': tweaks[tidx + 1], })