1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 # ##### END GPL LICENSE BLOCK #####
20 from Mathutils import Vector
22 # TODO, have these in a more general module
23 from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
25 empty_layer = [False] * 32
27 def auto_class(slots, name="ContainerClass", class_dict=None):
30 class_dict = class_dict.copy()
34 class_dict["__slots__"] = tuple(slots)
36 return type(name, (object,), class_dict)
38 def auto_class_instance(slots, name="ContainerClass", class_dict=None):
39 return auto_class(slots, name, class_dict)()
42 def _bone_class_instance_update(self):
43 ''' Re-Assigns bones from the blender data
47 pbones = self.obj.pose.bones
48 ebones = arm.edit_bones
50 for member in self.attr_names:
51 name = getattr(self, member, None)
53 setattr(self, member + "_b", bbones.get(name, None))
54 setattr(self, member + "_p", pbones.get(name, None))
55 setattr(self, member + "_e", ebones.get(name, None))
58 def _bone_class_instance_rename(self, attr, new_name):
59 ''' Rename bones, editmode only
62 if self.obj.mode != 'EDIT':
63 raise Exception("Only rename in editmode supported")
65 ebone = getattr(self, attr + "_e")
68 # we may not get what is asked for so get the name from the editbone
69 setattr(self, attr, ebone.name)
72 def _bone_class_instance_copy(self, from_prefix="", to_prefix=""):
77 for attr in self.attr_names:
78 bone_name_orig = getattr(self, attr)
79 ebone = getattr(self, attr + "_e")
80 # orig_names[attr] = bone_name_orig
84 bone_name = from_prefix + bone_name_orig
85 ebone.name = bone_name
86 bone_name = ebone.name # cant be sure we get what we ask for
88 bone_name = bone_name_orig
90 setattr(self, attr, bone_name)
92 new_slot_ls.append(attr)
93 from_name_ls.append(bone_name)
94 bone_name_orig = bone_name_orig.replace("ORG-", "") # XXX - we need a better way to do this
95 new_name_ls.append(to_prefix + bone_name_orig)
97 new_bones = copy_bone_simple_list(self.obj.data, from_name_ls, new_name_ls, True)
98 new_bc = bone_class_instance(self.obj, new_slot_ls)
100 for i, attr in enumerate(new_slot_ls):
102 setattr(new_bc, attr + "_e", ebone)
103 setattr(new_bc, attr, ebone.name)
107 def _bone_class_instance_names(self):
108 return [getattr(self, attr) for attr in self.attr_names]
110 def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_prop="blend"):
112 Use for blending bone chains.
114 blend_target = (bone_name, bone_property)
115 default to the last bone, blend prop
117 XXX - toggles editmode, need to re-validate all editbones :(
120 if self.attr_names != from_bc.attr_names or self.attr_names != to_bc.attr_names:
121 raise Exception("can only blend between matching chains")
123 apply_bones = [getattr(self, attr) for attr in self.attr_names]
124 from_bones = [getattr(from_bc, attr) for attr in from_bc.attr_names]
125 to_bones = [getattr(to_bc, attr) for attr in to_bc.attr_names]
127 blend_bone_list(self.obj, apply_bones, from_bones, to_bones, target_bone, target_prop)
129 def bone_class_instance(obj, slots, name="BoneContainer"):
130 attr_names = tuple(slots) # dont modify the original
131 slots = list(slots) # dont modify the original
132 for i in range(len(slots)):
134 slots.append(member + "_b") # bone bone
135 slots.append(member + "_p") # pose bone
136 slots.append(member + "_e") # edit bone
140 "attr_names":attr_names, \
141 "update":_bone_class_instance_update, \
142 "rename":_bone_class_instance_rename, \
143 "names":_bone_class_instance_names, \
144 "copy":_bone_class_instance_copy, \
145 "blend":_bone_class_instance_blend, \
148 instance = auto_class_instance(slots, name, class_dict)
151 def gen_none(obj, orig_bone_name):
154 def get_bone_data(obj, bone_name):
156 pbone = obj.pose.bones[bone_name]
157 if obj.mode == 'EDIT':
158 bone = arm.edit_bones[bone_name]
160 bone = arm.bones[bone_name]
162 return arm, pbone, bone
164 def copy_bone_simple(arm, from_bone, name, parent=False):
165 ebone = arm.edit_bones[from_bone]
166 ebone_new = arm.edit_bones.new(name)
169 ebone_new.connected = ebone.connected
170 ebone_new.parent = ebone.parent
172 ebone_new.head = ebone.head
173 ebone_new.tail = ebone.tail
174 ebone_new.roll = ebone.roll
178 def copy_bone_simple_list(arm, from_bones, to_bones, parent=False):
181 if len(from_bones) != len(to_bones):
182 raise Exception("bone list sizes must match")
184 copy_bones = [copy_bone_simple(arm, bone_name, to_bones[i], True) for i, bone_name in enumerate(from_bones)]
186 # now we need to re-parent
187 for ebone in copy_bones:
188 parent = ebone.parent
191 i = from_bones.index(parent.name)
198 ebone.parent = copy_bones[i]
202 def blend_bone_list(obj, apply_bones, from_bones, to_bones, target_bone=None, target_prop="blend"):
204 if obj.mode == 'EDIT':
205 raise Exception("blending cant be called in editmode")
207 # setup the blend property
208 if target_bone is None:
209 target_bone = apply_bones[-1] # default to the last bone
211 prop_pbone = obj.pose.bones[target_bone]
212 if prop_pbone.get(target_bone, None) is None:
213 prop = rna_idprop_ui_prop_get(prop_pbone, target_prop, create=True)
214 prop_pbone[target_prop] = 0.5
215 prop["soft_min"] = 0.0
216 prop["soft_max"] = 1.0
218 driver_path = prop_pbone.path_to_id() + ('["%s"]' % target_prop)
220 def blend_target(driver):
221 tar = driver.targets.new()
222 tar.name = target_bone
223 tar.id_type = 'OBJECT'
225 tar.rna_path = driver_path
227 def blend_location(new_pbone, from_bone_name, to_bone_name):
228 con = new_pbone.constraints.new('COPY_LOCATION')
230 con.subtarget = from_bone_name
232 con = new_pbone.constraints.new('COPY_LOCATION')
234 con.subtarget = to_bone_name
236 fcurve = con.driver_add("influence", 0)
237 driver = fcurve.driver
238 driver.type = 'AVERAGE'
239 fcurve.modifiers.remove(0) # grr dont need a modifier
243 def blend_rotation(new_pbone, from_bone_name, to_bone_name):
244 con = new_pbone.constraints.new('COPY_ROTATION')
246 con.subtarget = from_bone_name
248 con = new_pbone.constraints.new('COPY_ROTATION')
250 con.subtarget = to_bone_name
252 fcurve = con.driver_add("influence", 0)
253 driver = fcurve.driver
254 driver.type = 'AVERAGE'
255 fcurve.modifiers.remove(0) # grr dont need a modifier
259 for i, new_bone_name in enumerate(apply_bones):
260 from_bone_name = from_bones[i]
261 to_bone_name = to_bones[i]
263 # allow skipping some bones by having None in the list
264 if None in (new_bone_name, from_bone_name, to_bone_name):
267 new_pbone = obj.pose.bones[new_bone_name]
269 if not new_pbone.bone.connected:
270 blend_location(new_pbone, from_bone_name, to_bone_name)
272 blend_rotation(new_pbone, from_bone_name, to_bone_name)
275 def add_stretch_to(obj, from_name, to_name, name):
277 Adds a bone that stretches from one to another
281 bpy.ops.object.mode_set(mode='EDIT')
284 stretch_ebone = arm.edit_bones.new(name)
285 stretch_name = stretch_ebone.name
288 head = stretch_ebone.head = arm.edit_bones[from_name].head.copy()
289 #tail = stretch_ebone.tail = arm.edit_bones[to_name].head.copy()
291 # annoying exception for zero length bones, since its using stretch_to the rest pose doesnt really matter
292 #if (head - tail).length < 0.1:
294 tail = stretch_ebone.tail = arm.edit_bones[from_name].tail.copy()
297 # Now for the constraint
298 bpy.ops.object.mode_set(mode='OBJECT')
299 from_pbone = obj.pose.bones[from_name]
300 to_pbone = obj.pose.bones[to_name]
302 stretch_pbone = obj.pose.bones[stretch_name]
304 con = stretch_pbone.constraints.new('COPY_LOCATION')
306 con.subtarget = from_name
308 con = stretch_pbone.constraints.new('STRETCH_TO')
310 con.subtarget = to_name
311 con.original_length = (head - tail).length
312 con.keep_axis = 'PLANE_X'
313 con.volume = 'NO_VOLUME'
315 bpy.ops.object.mode_set(mode=mode_orig)
318 def add_pole_target_bone(obj, base_name, name, mode='CROSS'):
320 Does not actually create a poll target, just the bone to use as a poll target
323 bpy.ops.object.mode_set(mode='EDIT')
327 poll_ebone = arm.edit_bones.new(base_name + "_poll")
328 base_ebone = arm.edit_bones[base_name]
329 poll_name = poll_ebone.name
330 parent_ebone = base_ebone.parent
332 base_head = base_ebone.head.copy()
333 base_tail = base_ebone.tail.copy()
334 base_dir = base_head - base_tail
336 parent_head = parent_ebone.head.copy()
337 parent_tail = parent_ebone.tail.copy()
338 parent_dir = parent_head - parent_tail
340 distance = (base_dir.length + parent_dir.length)
343 offset = base_dir.copy().normalize() - parent_dir.copy().normalize()
344 offset.length = distance
346 offset = Vector(0,0,0)
352 setattr(offset, mode[1].lower(), val)
354 poll_ebone.head = base_head + offset
355 poll_ebone.tail = base_head + (offset * (1.0 - (1.0 / 4.0)))
357 bpy.ops.object.mode_set(mode=mode_orig)
362 def generate_rig(context, obj_orig, prefix="ORG-"):
363 from collections import OrderedDict
365 global_undo = context.user_preferences.edit.global_undo
366 context.user_preferences.edit.global_undo = False
368 bpy.ops.object.mode_set(mode='OBJECT')
371 # copy object and data
372 obj_orig.selected = False
373 obj = obj_orig.copy()
374 obj.data = obj_orig.data.copy()
375 scene = context.scene
376 scene.objects.link(obj)
377 scene.objects.active = obj
382 # original name mapping
385 bpy.ops.object.mode_set(mode='EDIT')
386 for bone in arm.edit_bones:
387 bone_name = bone.name
388 bone.name = prefix + bone_name
389 base_names[bone.name] = bone_name # new -> old mapping
390 bpy.ops.object.mode_set(mode='OBJECT')
393 # value: {type:definition, ...}
394 # where type is the submodule name - leg, arm etc
395 # and definition is a list of bone names
396 bone_definitions = {}
399 # value: [functions, ...]
400 # each function is from the module. eg leg.ik, arm.main
403 # inspect all bones and assign their definitions before modifying
404 for pbone in obj.pose.bones:
405 bone_name = pbone.name
406 bone_type = obj.pose.bones[bone_name].get("type", "")
407 bone_type_list = [bt for bt in bone_type.replace(",", " ").split()]
409 for bone_type in bone_type_list:
410 type_pair = bone_type.split(".")
412 # 'leg.ik' will look for an ik function in the leg module
413 # 'leg' will look up leg.main
414 if len(type_pair) == 1:
415 type_pair = type_pair[0], "main"
417 submod_name, func_name = type_pair
419 # from rigify import leg
420 submod = __import__(name="%s.%s" % (__package__, submod_name), fromlist=[submod_name])
423 bone_def_dict = bone_definitions.setdefault(bone_name, {})
425 # Only calculate bone definitions once
426 if submod_name not in bone_def_dict:
427 metarig_definition_func = getattr(submod, "metarig_definition")
428 bone_def_dict[submod_name] = metarig_definition_func(obj, bone_name)
431 bone_typeinfo = bone_typeinfos.setdefault(bone_name, [])
432 type_func = getattr(submod, func_name)
433 bone_typeinfo.append((submod_name, type_func))
436 # now we have all the info about bones we can start operating on them
438 for pbone in obj.pose.bones:
439 bone_name = pbone.name
441 if bone_name not in bone_typeinfos:
444 bone_def_dict = bone_definitions[bone_name]
446 # Only blend results from the same submodule, eg.
447 # leg.ik and arm.fk could not be blended.
448 results = OrderedDict()
450 for submod_name, type_func in bone_typeinfos[bone_name]:
451 # this bones definition of the current typeinfo
452 definition = bone_def_dict[submod_name]
454 bpy.ops.object.mode_set(mode='EDIT')
455 ret = type_func(obj, definition, base_names)
456 bpy.ops.object.mode_set(mode='OBJECT')
459 result_submod = results.setdefault(submod_name, [])
461 if result_submod and len(result_submod[-1]) != len(ret):
462 raise Exception("bone lists not compatible: %s, %s" % (result_submod[-1], ret))
464 result_submod.append(ret)
466 for result_submod in results.values():
468 definition = bone_def_dict[submod_name]
470 if len(result_submod) == 2:
471 blend_bone_list(obj, definition, result_submod[0], result_submod[1])
473 # needed to update driver deps
474 # context.scene.update()
478 # obj.restrict_view = True
479 obj.data.draw_axes = False
481 context.user_preferences.edit.global_undo = global_undo
486 def write_meta_rig(obj, func_name="metarig_template"):
487 ''' Must be in editmode
491 code.append("def %s():" % func_name)
492 code.append(" # generated by rigify.write_meta_rig")
493 bpy.ops.object.mode_set(mode='EDIT')
494 code.append(" bpy.ops.object.mode_set(mode='EDIT')")
496 code.append(" obj = bpy.context.object")
497 code.append(" arm = obj.data")
500 # write parents first
501 bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones]
502 bones.sort(key=lambda item: item[0])
503 bones = [item[1] for item in bones]
507 for bone_name in bones:
508 bone = arm.edit_bones[bone_name]
509 code.append(" bone = arm.edit_bones.new('%s')" % bone.name)
510 code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.toTuple(4))
511 code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.toTuple(4))
512 code.append(" bone.roll = %.4f" % bone.roll)
513 code.append(" bone.connected = %s" % str(bone.connected))
515 code.append(" bone.parent = arm.edit_bones['%s']" % bone.parent.name)
517 bpy.ops.object.mode_set(mode='OBJECT')
519 code.append(" bpy.ops.object.mode_set(mode='OBJECT')")
521 for bone_name in bones:
522 pbone = obj.pose.bones[bone_name]
523 pbone_written = False
525 # Only 1 level of props, simple types supported
526 for key, value in pbone.items():
527 if key.startswith("_"):
530 if type(value) not in (float, str, int):
531 print("Unsupported ID Prop:", str((key, value)))
534 if type(value) == str:
535 value = "'" + value + "'"
537 if not pbone_written: # only write bones we need
538 code.append(" pbone = obj.pose.bones['%s']" % bone_name)
540 code.append(" pbone['%s'] = %s" % (key, value))
542 return "\n".join(code)
545 def generate_test(context):
549 scene = context.scene
550 def create_empty_armature(name):
551 obj_new = bpy.data.add_object('ARMATURE', name)
552 armature = bpy.data.add_armature(name)
553 obj_new.data = armature
554 scene.objects.link(obj_new)
555 scene.objects.active = obj_new
557 files = os.listdir(os.path.dirname(__file__))
559 if f.startswith("_"):
562 if not f.endswith(".py"):
566 submodule = __import__(name="%s.%s" % (__package__, module_name), fromlist=[module_name])
568 metarig_template = getattr(submodule, "metarig_template", None)
571 create_empty_armature("meta_" + module_name) # sets active
574 obj_new = generate_rig(context, obj)
576 new_objects.append((obj, obj_new))
578 print("note: rig type '%s' has no metarig_template(), can't test this", module_name)
583 def generate_test_all(context):
585 import graphviz_export
588 reload(graphviz_export)
590 new_objects = rigify.generate_test(context)
592 base_name = os.path.splitext(bpy.data.filename)[0]
593 for obj, obj_new in new_objects:
594 for obj in (obj, obj_new):
595 fn = base_name + "-" + bpy.utils.clean_name(obj.name)
597 path_dot = fn + ".dot"
598 path_png = fn + ".png"
599 saved = graphviz_export.graph_armature(obj, path_dot, CONSTRAINTS=True, DRIVERS=True)
602 # os.system("dot -Tpng %s > %s; eog %s" % (path_dot, path_png, path_png))
605 for obj, obj_new in new_objects:
606 obj.data.drawtype = 'STICK'
608 obj_new.location[1] += i
609 obj_new.selected = False
614 if __name__ == "__main__":
615 generate_rig(bpy.context, bpy.context.object)