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 21import re 22 23import bpy 24from mathutils import Vector 25 26from ..utils import MetarigError 27from ..utils import copy_bone 28from ..utils import connected_children_names 29from ..utils import strip_org, make_mechanism_name, make_deformer_name 30from ..utils import create_widget, create_limb_widget 31 32from ...utils.mechanism import make_property 33 34class Rig: 35 """ A finger rig. It takes a single chain of bones. 36 This is a control and deformation rig. 37 """ 38 def __init__(self, obj, bone, params): 39 """ Gather and validate data about the rig. 40 """ 41 self.obj = obj 42 self.org_bones = [bone] + connected_children_names(obj, bone) 43 self.params = params 44 45 if len(self.org_bones) <= 1: 46 raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones" % (strip_org(bone))) 47 48 # Get user-specified layers, if they exist 49 if params.separate_extra_layers: 50 self.ex_layers = list(params.extra_layers) 51 else: 52 self.ex_layers = None 53 54 # Get other rig parameters 55 self.primary_rotation_axis = params.primary_rotation_axis 56 self.use_digit_twist = params.use_digit_twist 57 58 def deform(self): 59 """ Generate the deformation rig. 60 Just a copy of the original bones, except the first digit which is a twist bone. 61 """ 62 bpy.ops.object.mode_set(mode='EDIT') 63 64 # Create the bones 65 # First bone is a twist bone 66 if self.use_digit_twist: 67 b1a = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01"))) 68 b1b = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02"))) 69 b1tip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip"))) 70 else: 71 b1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0]))) 72 73 # The rest are normal 74 bones = [] 75 for bone in self.org_bones[1:]: 76 bones += [copy_bone(self.obj, bone, make_deformer_name(strip_org(bone)))] 77 78 # Position bones 79 eb = self.obj.data.edit_bones 80 if self.use_digit_twist: 81 b1a_e = eb[b1a] 82 b1b_e = eb[b1b] 83 b1tip_e = eb[b1tip] 84 85 b1tip_e.use_connect = False 86 b1tip_e.tail += Vector((0.1, 0, 0)) 87 b1tip_e.head = b1b_e.tail 88 b1tip_e.length = b1a_e.length / 4 89 90 center = (b1a_e.head + b1a_e.tail) / 2 91 b1a_e.tail = center 92 b1b_e.use_connect = False 93 b1b_e.head = center 94 95 # Parenting 96 if self.use_digit_twist: 97 b1b_e.parent = eb[self.org_bones[0]] 98 b1tip_e.parent = eb[self.org_bones[0]] 99 else: 100 eb[b1].use_connect = False 101 eb[b1].parent = eb[self.org_bones[0]] 102 103 for (ba, bb) in zip(bones, self.org_bones[1:]): 104 eb[ba].use_connect = False 105 eb[ba].parent = eb[bb] 106 107 # Constraints 108 if self.use_digit_twist: 109 bpy.ops.object.mode_set(mode='OBJECT') 110 pb = self.obj.pose.bones 111 112 b1a_p = pb[b1a] 113 114 con = b1a_p.constraints.new('COPY_LOCATION') 115 con.name = "copy_location" 116 con.target = self.obj 117 con.subtarget = self.org_bones[0] 118 119 con = b1a_p.constraints.new('COPY_SCALE') 120 con.name = "copy_scale" 121 con.target = self.obj 122 con.subtarget = self.org_bones[0] 123 124 con = b1a_p.constraints.new('DAMPED_TRACK') 125 con.name = "track_to" 126 con.target = self.obj 127 con.subtarget = b1tip 128 129 def control(self): 130 """ Generate the control rig. 131 """ 132 bpy.ops.object.mode_set(mode='EDIT') 133 134 # Figure out the name for the control bone (remove the last .##) 135 ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.org_bones[0])[::-1], count=1)[::-1] 136 137 # Create the bones 138 ctrl = copy_bone(self.obj, self.org_bones[0], ctrl_name) 139 140 helpers = [] 141 bones = [] 142 for bone in self.org_bones: 143 bones += [copy_bone(self.obj, bone, strip_org(bone))] 144 helpers += [copy_bone(self.obj, bone, make_mechanism_name(strip_org(bone)))] 145 146 # Position bones 147 eb = self.obj.data.edit_bones 148 149 length = 0.0 150 for bone in helpers: 151 length += eb[bone].length 152 eb[bone].length /= 2 153 154 eb[ctrl].length = length * 1.5 155 156 # Parent bones 157 prev = eb[self.org_bones[0]].parent 158 for (b, h) in zip(bones, helpers): 159 b_e = eb[b] 160 h_e = eb[h] 161 b_e.use_connect = False 162 h_e.use_connect = False 163 164 b_e.parent = h_e 165 h_e.parent = prev 166 167 prev = b_e 168 169 # Transform locks and rotation mode 170 bpy.ops.object.mode_set(mode='OBJECT') 171 pb = self.obj.pose.bones 172 173 for bone in bones[1:]: 174 pb[bone].lock_location = True, True, True 175 176 if pb[self.org_bones[0]].bone.use_connect is True: 177 pb[bones[0]].lock_location = True, True, True 178 179 pb[ctrl].lock_scale = True, False, True 180 181 for bone in helpers: 182 pb[bone].rotation_mode = 'XYZ' 183 184 # Drivers 185 i = 1 186 val = 1.2 / (len(self.org_bones) - 1) 187 for bone in helpers: 188 # Add custom prop 189 prop_name = "bend_%02d" % i 190 if i == 1: 191 propval = 0.0 192 else: 193 propval = val 194 195 make_property(pb[ctrl], prop_name, propval) 196 197 # Add driver 198 if 'X' in self.primary_rotation_axis: 199 fcurve = pb[bone].driver_add("rotation_euler", 0) 200 elif 'Y' in self.primary_rotation_axis: 201 fcurve = pb[bone].driver_add("rotation_euler", 1) 202 else: 203 fcurve = pb[bone].driver_add("rotation_euler", 2) 204 205 driver = fcurve.driver 206 driver.type = 'SCRIPTED' 207 208 var = driver.variables.new() 209 var.name = "ctrl_y" 210 var.targets[0].id_type = 'OBJECT' 211 var.targets[0].id = self.obj 212 var.targets[0].data_path = pb[ctrl].path_from_id() + '.scale[1]' 213 214 var = driver.variables.new() 215 var.name = "bend" 216 var.targets[0].id_type = 'OBJECT' 217 var.targets[0].id = self.obj 218 var.targets[0].data_path = pb[ctrl].path_from_id() + '["' + prop_name + '"]' 219 220 if '-' in self.primary_rotation_axis: 221 driver.expression = "-(1.0-ctrl_y) * bend * 3.14159 * 2" 222 else: 223 driver.expression = "(1.0-ctrl_y) * bend * 3.14159 * 2" 224 225 i += 1 226 227 # Constraints 228 con = pb[helpers[0]].constraints.new('COPY_LOCATION') 229 con.name = "copy_location" 230 con.target = self.obj 231 con.subtarget = ctrl 232 233 con = pb[helpers[0]].constraints.new('COPY_ROTATION') 234 con.name = "copy_rotation" 235 con.target = self.obj 236 con.subtarget = ctrl 237 238 # Constrain org bones to the control bones 239 for (bone, org) in zip(bones, self.org_bones): 240 con = pb[org].constraints.new('COPY_TRANSFORMS') 241 con.name = "copy_transforms" 242 con.target = self.obj 243 con.subtarget = bone 244 245 # Set layers for extra control bones 246 if self.ex_layers: 247 for bone in bones: 248 pb[bone].bone.layers = self.ex_layers 249 250 # Create control widgets 251 w = create_widget(self.obj, ctrl) 252 if w is not None: 253 mesh = w.data 254 verts = [(0, 0, 0), (0, 1, 0), (0.05, 1, 0), (0.05, 1.1, 0), (-0.05, 1.1, 0), (-0.05, 1, 0)] 255 if 'Z' in self.primary_rotation_axis: 256 # Flip x/z coordinates 257 temp = [] 258 for v in verts: 259 temp += [(v[2], v[1], v[0])] 260 verts = temp 261 edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 1)] 262 mesh.from_pydata(verts, edges, []) 263 mesh.update() 264 265 for bone in bones: 266 create_limb_widget(self.obj, bone) 267 268 def generate(self): 269 """ Generate the rig. 270 Do NOT modify any of the original bones, except for adding constraints. 271 The main armature should be selected and active before this is called. 272 """ 273 self.deform() 274 self.control() 275 276 277def add_parameters(params): 278 """ Add the parameters of this rig type to the 279 RigifyParameters PropertyGroup 280 """ 281 items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')] 282 params.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X') 283 284 params.separate_extra_layers = bpy.props.BoolProperty(name="Separate Secondary Control Layers:", default=False, description="Enable putting the secondary controls on a separate layer from the primary controls") 285 params.extra_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the secondary controls to be on") 286 287 params.use_digit_twist = bpy.props.BoolProperty(name="Digit Twist", default=True, description="Generate the dual-bone twist setup for the first finger digit") 288 289 290def parameters_ui(layout, params): 291 """ Create the ui for the rig parameters. 292 """ 293 r = layout.row() 294 r.prop(params, "separate_extra_layers") 295 296 r = layout.row() 297 r.active = params.separate_extra_layers 298 299 col = r.column(align=True) 300 row = col.row(align=True) 301 row.prop(params, "extra_layers", index=0, toggle=True, text="") 302 row.prop(params, "extra_layers", index=1, toggle=True, text="") 303 row.prop(params, "extra_layers", index=2, toggle=True, text="") 304 row.prop(params, "extra_layers", index=3, toggle=True, text="") 305 row.prop(params, "extra_layers", index=4, toggle=True, text="") 306 row.prop(params, "extra_layers", index=5, toggle=True, text="") 307 row.prop(params, "extra_layers", index=6, toggle=True, text="") 308 row.prop(params, "extra_layers", index=7, toggle=True, text="") 309 row = col.row(align=True) 310 row.prop(params, "extra_layers", index=16, toggle=True, text="") 311 row.prop(params, "extra_layers", index=17, toggle=True, text="") 312 row.prop(params, "extra_layers", index=18, toggle=True, text="") 313 row.prop(params, "extra_layers", index=19, toggle=True, text="") 314 row.prop(params, "extra_layers", index=20, toggle=True, text="") 315 row.prop(params, "extra_layers", index=21, toggle=True, text="") 316 row.prop(params, "extra_layers", index=22, toggle=True, text="") 317 row.prop(params, "extra_layers", index=23, toggle=True, text="") 318 319 col = r.column(align=True) 320 row = col.row(align=True) 321 row.prop(params, "extra_layers", index=8, toggle=True, text="") 322 row.prop(params, "extra_layers", index=9, toggle=True, text="") 323 row.prop(params, "extra_layers", index=10, toggle=True, text="") 324 row.prop(params, "extra_layers", index=11, toggle=True, text="") 325 row.prop(params, "extra_layers", index=12, toggle=True, text="") 326 row.prop(params, "extra_layers", index=13, toggle=True, text="") 327 row.prop(params, "extra_layers", index=14, toggle=True, text="") 328 row.prop(params, "extra_layers", index=15, toggle=True, text="") 329 row = col.row(align=True) 330 row.prop(params, "extra_layers", index=24, toggle=True, text="") 331 row.prop(params, "extra_layers", index=25, toggle=True, text="") 332 row.prop(params, "extra_layers", index=26, toggle=True, text="") 333 row.prop(params, "extra_layers", index=27, toggle=True, text="") 334 row.prop(params, "extra_layers", index=28, toggle=True, text="") 335 row.prop(params, "extra_layers", index=29, toggle=True, text="") 336 row.prop(params, "extra_layers", index=30, toggle=True, text="") 337 row.prop(params, "extra_layers", index=31, toggle=True, text="") 338 339 r = layout.row() 340 r.label(text="Bend rotation axis:") 341 r.prop(params, "primary_rotation_axis", text="") 342 343 col = layout.column() 344 col.prop(params, "use_digit_twist") 345 346 347def create_sample(obj): 348 # generated by rigify.utils.write_metarig 349 bpy.ops.object.mode_set(mode='EDIT') 350 arm = obj.data 351 352 bones = {} 353 354 bone = arm.edit_bones.new('finger.01') 355 bone.head[:] = 0.0000, 0.0000, 0.0000 356 bone.tail[:] = 0.2529, 0.0000, 0.0000 357 bone.roll = 3.1416 358 bone.use_connect = False 359 bones['finger.01'] = bone.name 360 bone = arm.edit_bones.new('finger.02') 361 bone.head[:] = 0.2529, 0.0000, 0.0000 362 bone.tail[:] = 0.4024, 0.0000, -0.0264 363 bone.roll = -2.9671 364 bone.use_connect = True 365 bone.parent = arm.edit_bones[bones['finger.01']] 366 bones['finger.02'] = bone.name 367 bone = arm.edit_bones.new('finger.03') 368 bone.head[:] = 0.4024, 0.0000, -0.0264 369 bone.tail[:] = 0.4975, -0.0000, -0.0610 370 bone.roll = -2.7925 371 bone.use_connect = True 372 bone.parent = arm.edit_bones[bones['finger.02']] 373 bones['finger.03'] = bone.name 374 375 bpy.ops.object.mode_set(mode='OBJECT') 376 pbone = obj.pose.bones[bones['finger.01']] 377 pbone.rigify_type = 'finger' 378 pbone.lock_location = (True, True, True) 379 pbone.lock_rotation = (False, False, False) 380 pbone.lock_rotation_w = False 381 pbone.lock_scale = (False, False, False) 382 pbone.rotation_mode = 'YZX' 383 pbone = obj.pose.bones[bones['finger.02']] 384 pbone.rigify_type = '' 385 pbone.lock_location = (False, False, False) 386 pbone.lock_rotation = (False, False, False) 387 pbone.lock_rotation_w = False 388 pbone.lock_scale = (False, False, False) 389 pbone.rotation_mode = 'YZX' 390 pbone = obj.pose.bones[bones['finger.03']] 391 pbone.rigify_type = '' 392 pbone.lock_location = (False, False, False) 393 pbone.lock_rotation = (False, False, False) 394 pbone.lock_rotation_w = False 395 pbone.lock_scale = (False, False, False) 396 pbone.rotation_mode = 'YZX' 397 398 bpy.ops.object.mode_set(mode='EDIT') 399 for bone in arm.edit_bones: 400 bone.select = False 401 bone.select_head = False 402 bone.select_tail = False 403 for b in bones: 404 bone = arm.edit_bones[bones[b]] 405 bone.select = True 406 bone.select_head = True 407 bone.select_tail = True 408 arm.edit_bones.active = bone 409