|
| 1 | +#====================== BEGIN GPL LICENSE BLOCK ====================== |
| 2 | +# |
| 3 | +# This program is free software; you can redistribute it and/or |
| 4 | +# modify it under the terms of the GNU General Public License |
| 5 | +# as published by the Free Software Foundation; either version 2 |
| 6 | +# of the License, or (at your option) any later version. |
| 7 | +# |
| 8 | +# This program is distributed in the hope that it will be useful, |
| 9 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | +# GNU General Public License for more details. |
| 12 | +# |
| 13 | +# You should have received a copy of the GNU General Public License |
| 14 | +# along with this program; if not, write to the Free Software Foundation, |
| 15 | +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 16 | +# |
| 17 | +#======================= END GPL LICENSE BLOCK ======================== |
| 18 | + |
| 19 | +# <pep8 compliant> |
| 20 | + |
| 21 | +import bpy |
| 22 | + |
| 23 | +from itertools import count, repeat |
| 24 | + |
| 25 | +from ..chain_rigs import TweakChainRig |
| 26 | + |
| 27 | +from ...utils.errors import MetarigError |
| 28 | +from ...utils.layers import ControlLayersOption |
| 29 | +from ...utils.naming import strip_org, make_deformer_name, make_mechanism_name |
| 30 | +from ...utils.bones import BoneDict, put_bone, align_bone_to_axis |
| 31 | +from ...utils.widgets_basic import create_circle_widget, create_cube_widget |
| 32 | +from ...utils.misc import map_list, map_apply |
| 33 | + |
| 34 | +from ...base_rig import * |
| 35 | + |
| 36 | + |
| 37 | +class Rig(TweakChainRig): |
| 38 | + """ |
| 39 | + Spine rig with fixed pivot, hip/chest controls and tweaks. |
| 40 | + """ |
| 41 | + |
| 42 | + bbone_segments = 8 |
| 43 | + |
| 44 | + def initialize(self): |
| 45 | + if len(self.bones.org) < 3: |
| 46 | + raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 or more bones" % (strip_org(self.base_bone))) |
| 47 | + |
| 48 | + # Check if user provided the pivot position |
| 49 | + self.pivot_pos = self.params.pivot_pos |
| 50 | + |
| 51 | + if not (0 < self.pivot_pos < len(self.bones.org)): |
| 52 | + raise MetarigError( |
| 53 | + "RIGIFY ERROR: please specify a valid pivot bone position" |
| 54 | + ) |
| 55 | + |
| 56 | + self.length = sum([self.get_bone(b).length for b in self.bones.org]) |
| 57 | + |
| 58 | + #################################################### |
| 59 | + # Main control bones |
| 60 | + |
| 61 | + @stage_generate_bones |
| 62 | + def make_control_chain(self): |
| 63 | + orgs = self.bones.org |
| 64 | + ctrl = self.bones.ctrl |
| 65 | + pivot = self.pivot_pos |
| 66 | + |
| 67 | + ctrl.master = self.make_torso_control_bone(orgs[pivot], 'torso') |
| 68 | + ctrl.hips = self.make_hips_control_bone(orgs[pivot-1], 'hips') |
| 69 | + ctrl.chest = self.make_chest_control_bone(orgs[pivot], 'chest') |
| 70 | + |
| 71 | + def make_torso_control_bone(self, org, name): |
| 72 | + name = self.copy_bone(org, name, parent=False) |
| 73 | + align_bone_to_axis(self.obj, name, 'y', self.length * 0.6) |
| 74 | + |
| 75 | + # Put the main control in the middle of the hip bone |
| 76 | + base_bone = self.get_bone(self.bones.org[0]) |
| 77 | + put_bone(self.obj, name, (base_bone.head + base_bone.tail) / 2) |
| 78 | + |
| 79 | + return name |
| 80 | + |
| 81 | + def make_hips_control_bone(self, org, name): |
| 82 | + name = self.copy_bone(org, name, parent=False) |
| 83 | + align_bone_to_axis(self.obj, name, 'y', self.length / 4, flip=True) |
| 84 | + return name |
| 85 | + |
| 86 | + def make_chest_control_bone(self, org, name): |
| 87 | + name = self.copy_bone(org, name, parent=False) |
| 88 | + align_bone_to_axis(self.obj, name, 'y', self.length / 3) |
| 89 | + return name |
| 90 | + |
| 91 | + @stage_parent_bones |
| 92 | + def parent_control_chain(self): |
| 93 | + ctrl = self.bones.ctrl |
| 94 | + org_parent = self.get_bone_parent(self.bones.org[0]) |
| 95 | + self.set_bone_parent(ctrl.master, org_parent) |
| 96 | + self.set_bone_parent(ctrl.hips, ctrl.master) |
| 97 | + self.set_bone_parent(ctrl.chest, ctrl.master) |
| 98 | + |
| 99 | + @stage_configure_bones |
| 100 | + def configure_control_chain(self): |
| 101 | + pass |
| 102 | + |
| 103 | + @stage_generate_widgets |
| 104 | + def make_control_widgets(self): |
| 105 | + ctrl = self.bones.ctrl |
| 106 | + mch = self.bones.mch |
| 107 | + self.make_master_widget(ctrl.master) |
| 108 | + self.make_control_widget(ctrl.hips, mch.wgt_hips) |
| 109 | + self.make_control_widget(ctrl.chest, mch.wgt_chest) |
| 110 | + |
| 111 | + def make_master_widget(self, ctrl): |
| 112 | + create_cube_widget( |
| 113 | + self.obj, ctrl, |
| 114 | + radius=0.5, |
| 115 | + bone_transform_name=None |
| 116 | + ) |
| 117 | + |
| 118 | + def make_control_widget(self, ctrl, wgt_mch): |
| 119 | + self.get_bone(ctrl).custom_shape_transform = self.get_bone(wgt_mch) |
| 120 | + |
| 121 | + create_circle_widget( |
| 122 | + self.obj, ctrl, |
| 123 | + radius=1.0, |
| 124 | + head_tail=0.75, |
| 125 | + with_line=False, |
| 126 | + bone_transform_name=wgt_mch |
| 127 | + ) |
| 128 | + |
| 129 | + #################################################### |
| 130 | + # MCH bones associated with main controls |
| 131 | + |
| 132 | + @stage_generate_bones |
| 133 | + def make_mch_control_bones(self): |
| 134 | + orgs = self.bones.org |
| 135 | + mch = self.bones.mch |
| 136 | + |
| 137 | + mch.pivot = self.make_mch_pivot_bone(orgs[self.pivot_pos], 'pivot') |
| 138 | + mch.wgt_hips = self.make_mch_widget_bone(orgs[0], 'WGT-hips') |
| 139 | + mch.wgt_chest = self.make_mch_widget_bone(orgs[-1], 'WGT-chest') |
| 140 | + |
| 141 | + def make_mch_pivot_bone(self, org, name): |
| 142 | + name = self.copy_bone(org, make_mechanism_name(name), parent=False) |
| 143 | + align_bone_to_axis(self.obj, name, 'y', self.length * 0.6 / 4) |
| 144 | + return name |
| 145 | + |
| 146 | + def make_mch_widget_bone(self, org, name): |
| 147 | + return self.copy_bone(org, make_mechanism_name(name), parent=False) |
| 148 | + |
| 149 | + @stage_parent_bones |
| 150 | + def parent_mch_control_bones(self): |
| 151 | + mch = self.bones.mch |
| 152 | + self.set_bone_parent(mch.pivot, mch.chain.chest[0]) |
| 153 | + self.set_bone_parent(mch.wgt_hips, mch.chain.hips[0]) |
| 154 | + self.set_bone_parent(mch.wgt_chest, mch.chain.chest[-1]) |
| 155 | + |
| 156 | + @stage_rig_bones |
| 157 | + def rig_mch_control_bones(self): |
| 158 | + mch = self.bones.mch |
| 159 | + # Is it actually intending to compute average of these, or is this really intentional? |
| 160 | + # This effectively adds rotations of the hip and chest controls. |
| 161 | + self.make_constraint(mch.pivot, 'COPY_TRANSFORMS', mch.chain.hips[-1], space='LOCAL') |
| 162 | + |
| 163 | + #################################################### |
| 164 | + # MCH chain for distributing hip & chest transform |
| 165 | + |
| 166 | + @stage_generate_bones |
| 167 | + def make_mch_chain(self): |
| 168 | + orgs = self.bones.org |
| 169 | + self.bones.mch.chain = BoneDict( |
| 170 | + hips = map_list(self.make_mch_bone, orgs[0:self.pivot_pos], repeat(True)), |
| 171 | + chest = map_list(self.make_mch_bone, orgs[self.pivot_pos:], repeat(False)), |
| 172 | + ) |
| 173 | + |
| 174 | + def make_mch_bone(self, org, is_hip): |
| 175 | + name = self.copy_bone(org, make_mechanism_name(strip_org(org)), parent=False) |
| 176 | + align_bone_to_axis(self.obj, name, 'y', self.length / 10, flip=is_hip) |
| 177 | + return name |
| 178 | + |
| 179 | + @stage_parent_bones |
| 180 | + def parent_mch_chain(self): |
| 181 | + master = self.bones.ctrl.master |
| 182 | + chain = self.bones.mch.chain |
| 183 | + self.parent_bone_chain([master, *reversed(chain.hips)]) |
| 184 | + self.parent_bone_chain([master, *chain.chest]) |
| 185 | + |
| 186 | + @stage_rig_bones |
| 187 | + def rig_mch_chain(self): |
| 188 | + ctrl = self.bones.ctrl |
| 189 | + chain = self.bones.mch.chain |
| 190 | + map_apply(self.rig_mch_bone, chain.hips, repeat(ctrl.hips), repeat(len(chain.hips))) |
| 191 | + map_apply(self.rig_mch_bone, chain.chest, repeat(ctrl.chest), repeat(len(chain.chest))) |
| 192 | + |
| 193 | + def rig_mch_bone(self, mch, control, count): |
| 194 | + self.make_constraint(mch, 'COPY_TRANSFORMS', control, space='LOCAL', influence=1/count) |
| 195 | + |
| 196 | + #################################################### |
| 197 | + # Tweak bones |
| 198 | + |
| 199 | + @stage_parent_bones |
| 200 | + def parent_tweak_chain(self): |
| 201 | + mch = self.bones.mch |
| 202 | + chain = mch.chain |
| 203 | + parents = [chain.hips[0], *chain.hips[0:-1], mch.pivot, *chain.chest[1:], chain.chest[-1]] |
| 204 | + map_apply(self.set_bone_parent, self.bones.ctrl.tweak, parents) |
| 205 | + |
| 206 | + @stage_configure_bones |
| 207 | + def configure_tweak_chain(self): |
| 208 | + super(Rig,self).configure_tweak_chain() |
| 209 | + |
| 210 | + ControlLayersOption.TWEAK.assign(self.params, self.obj, self.bones.ctrl.tweak) |
| 211 | + |
| 212 | + #################################################### |
| 213 | + # Deform bones |
| 214 | + |
| 215 | + @stage_configure_bones |
| 216 | + def configure_bbone_chain(self): |
| 217 | + self.get_bone(self.bones.deform[0]).bone.bbone_in = 0.0 |
| 218 | + |
| 219 | + #################################################### |
| 220 | + # SETTINGS |
| 221 | + |
| 222 | + @classmethod |
| 223 | + def add_parameters(self, params): |
| 224 | + """ Add the parameters of this rig type to the |
| 225 | + RigifyParameters PropertyGroup |
| 226 | + """ |
| 227 | + |
| 228 | + params.pivot_pos = bpy.props.IntProperty( |
| 229 | + name='pivot_position', |
| 230 | + default=2, |
| 231 | + min=0, |
| 232 | + description='Position of the torso control and pivot point' |
| 233 | + ) |
| 234 | + |
| 235 | + # Setting up extra layers for the FK and tweak |
| 236 | + ControlLayersOption.TWEAK.add_parameters(params) |
| 237 | + |
| 238 | + @classmethod |
| 239 | + def parameters_ui(self, layout, params): |
| 240 | + """ Create the ui for the rig parameters.""" |
| 241 | + |
| 242 | + r = layout.row() |
| 243 | + r.prop(params, "pivot_pos") |
| 244 | + |
| 245 | + ControlLayersOption.TWEAK.parameters_ui(layout, params) |
| 246 | + |
| 247 | + |
| 248 | +def create_sample(obj): |
| 249 | + # generated by rigify.utils.write_metarig |
| 250 | + bpy.ops.object.mode_set(mode='EDIT') |
| 251 | + arm = obj.data |
| 252 | + |
| 253 | + bones = {} |
| 254 | + |
| 255 | + bone = arm.edit_bones.new('spine') |
| 256 | + bone.head[:] = 0.0000, 0.0552, 1.0099 |
| 257 | + bone.tail[:] = 0.0000, 0.0172, 1.1573 |
| 258 | + bone.roll = 0.0000 |
| 259 | + bone.use_connect = False |
| 260 | + bones['spine'] = bone.name |
| 261 | + |
| 262 | + bone = arm.edit_bones.new('spine.001') |
| 263 | + bone.head[:] = 0.0000, 0.0172, 1.1573 |
| 264 | + bone.tail[:] = 0.0000, 0.0004, 1.2929 |
| 265 | + bone.roll = 0.0000 |
| 266 | + bone.use_connect = True |
| 267 | + bone.parent = arm.edit_bones[bones['spine']] |
| 268 | + bones['spine.001'] = bone.name |
| 269 | + |
| 270 | + bone = arm.edit_bones.new('spine.002') |
| 271 | + bone.head[:] = 0.0000, 0.0004, 1.2929 |
| 272 | + bone.tail[:] = 0.0000, 0.0059, 1.4657 |
| 273 | + bone.roll = 0.0000 |
| 274 | + bone.use_connect = True |
| 275 | + bone.parent = arm.edit_bones[bones['spine.001']] |
| 276 | + bones['spine.002'] = bone.name |
| 277 | + |
| 278 | + bone = arm.edit_bones.new('spine.003') |
| 279 | + bone.head[:] = 0.0000, 0.0059, 1.4657 |
| 280 | + bone.tail[:] = 0.0000, 0.0114, 1.6582 |
| 281 | + bone.roll = 0.0000 |
| 282 | + bone.use_connect = True |
| 283 | + bone.parent = arm.edit_bones[bones['spine.002']] |
| 284 | + bones['spine.003'] = bone.name |
| 285 | + |
| 286 | + |
| 287 | + bpy.ops.object.mode_set(mode='OBJECT') |
| 288 | + pbone = obj.pose.bones[bones['spine']] |
| 289 | + pbone.rigify_type = 'spines.basic_spine' |
| 290 | + pbone.lock_location = (False, False, False) |
| 291 | + pbone.lock_rotation = (False, False, False) |
| 292 | + pbone.lock_rotation_w = False |
| 293 | + pbone.lock_scale = (False, False, False) |
| 294 | + pbone.rotation_mode = 'QUATERNION' |
| 295 | + |
| 296 | + try: |
| 297 | + pbone.rigify_parameters.tweak_layers = [False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False] |
| 298 | + except AttributeError: |
| 299 | + pass |
| 300 | + pbone = obj.pose.bones[bones['spine.001']] |
| 301 | + pbone.rigify_type = '' |
| 302 | + pbone.lock_location = (False, False, False) |
| 303 | + pbone.lock_rotation = (False, False, False) |
| 304 | + pbone.lock_rotation_w = False |
| 305 | + pbone.lock_scale = (False, False, False) |
| 306 | + pbone.rotation_mode = 'QUATERNION' |
| 307 | + pbone = obj.pose.bones[bones['spine.002']] |
| 308 | + pbone.rigify_type = '' |
| 309 | + pbone.lock_location = (False, False, False) |
| 310 | + pbone.lock_rotation = (False, False, False) |
| 311 | + pbone.lock_rotation_w = False |
| 312 | + pbone.lock_scale = (False, False, False) |
| 313 | + pbone.rotation_mode = 'QUATERNION' |
| 314 | + pbone = obj.pose.bones[bones['spine.003']] |
| 315 | + pbone.rigify_type = '' |
| 316 | + pbone.lock_location = (False, False, False) |
| 317 | + pbone.lock_rotation = (False, False, False) |
| 318 | + pbone.lock_rotation_w = False |
| 319 | + pbone.lock_scale = (False, False, False) |
| 320 | + pbone.rotation_mode = 'QUATERNION' |
| 321 | + |
| 322 | + bpy.ops.object.mode_set(mode='EDIT') |
| 323 | + for bone in arm.edit_bones: |
| 324 | + bone.select = False |
| 325 | + bone.select_head = False |
| 326 | + bone.select_tail = False |
| 327 | + for b in bones: |
| 328 | + bone = arm.edit_bones[bones[b]] |
| 329 | + bone.select = True |
| 330 | + bone.select_head = True |
| 331 | + bone.select_tail = True |
| 332 | + arm.edit_bones.active = bone |
0 commit comments