From c60d7e325716a308a7663428df5b5be04ef0d6cc Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Sat, 10 Jun 2017 14:29:54 +0200 Subject: Rigify 0.5 advanced generation options, fixes and improvements. removed CREDITS and README files --- rigify/CREDITS | 32 ------- rigify/README | 250 ----------------------------------------------------- rigify/__init__.py | 26 ++++-- rigify/generate.py | 70 ++++++++++++--- rigify/ui.py | 29 +++++-- rigify/utils.py | 8 +- 6 files changed, 107 insertions(+), 308 deletions(-) delete mode 100644 rigify/CREDITS delete mode 100644 rigify/README diff --git a/rigify/CREDITS b/rigify/CREDITS deleted file mode 100644 index 38f6de46..00000000 --- a/rigify/CREDITS +++ /dev/null @@ -1,32 +0,0 @@ -A big thank you to all the people listed here for supporting Rigify. - -Original prototyping and development, and Python API support: -- Campbell Barton - -Development: -- PitchiPoy Animation Productions -- Kfir Merlaub -- Tamir Lousky - -General financial support: -- Benjamin Tolputt -- Nesterenko Viktoriya -- Jeff Hogan -- PitchiPoy Animation Productions - -IK/FK snapping financial support: -- Benjamin Tolputt -- Nesterenko Viktoriya -- Leslie Chih -- Isaac Ah-Loe -- Casey "TheLorax" Jones - -### Rigify Version 0.5 ### -Development: -- Lucio Rossi - -Design: -- Ivan Cappiello - -General financial support: -- Mad Entertainment Animation \ No newline at end of file diff --git a/rigify/README b/rigify/README deleted file mode 100644 index f8cb7656..00000000 --- a/rigify/README +++ /dev/null @@ -1,250 +0,0 @@ -INTRODUCTION ------------- -Rigify is an auto-rigging system based on a "building blocks" paradigm. The -user can create a rig by putting together any combination of rig types, in any -configuration that they want. - -A rig type is something like "biped arm" or "spine" or "finger". - -The input to the Rigify system is something called a "metarig". It is an -armature that contains data about how to construct the rig. In particular, it -contains bones in the basic configuration of the rig, with some bones tagged -to indicate the rig type. - -For example, a metarig might contain a chain of three bones, the root-most of -which is tagged as being a biped arm. When given as input to Rigify, Rigify -will then generate a fully-featured biped arm rig in the same position and -proportions as the 3-bone chain. - -One could also have another chain of bones, the root-most of which is tagged as -being a spine. And the root-most bone of the arm chain could be the child of -any of those spine bones. Then the rig that Rigify generates would be a -spine rig with an arm rig attached to it. - - -THE GUTS OF RIGIFY, SUMMARIZED ------------------------------- -The concept behind rigify is fairly simple. It recieves an armature as input -with some of the bones tagged as being certain rig types (arm, leg, etc.) - -When Rigify recieves that armature as input, the first thing it does is -duplicate the armature. From here on out, the original armature is totally -ignored. Only the duplicate is used. And this duplicate armature object will -become the generated rig. - -Rigify next prepends "ORG-" to all of the bones. These are the "original" -bones of the metarig, and they are used as the glue between rig types, as I -will explain later. - -Rigify then generates the rig in two passes. The first pass is the -"information gathering" stage. - -The information gathering stage doesn't modify the armature at all. It simply -gathers information about it. Or, rather, it lets the rig types gather -information about it. -It traverses the bones in a root-most to leaf-most order, and whenever it -stumbles upon a bone that has a rig type tagged on it, it creates a rig-type -python object (rig types will be explained further down) for that rig type, -and executes the resulting python object's information gathering code. - -At the end of the information gathering stage, Rigify has a collection of -python objects, each of which know all the information they need to generate -their own bit of the rig. - -The next stage is the rig generation stage. This part is pretty simple. All -Rigify does is it loops over all of the rig-type python objects that it created -in the previous stage (also in root-most to leaf-most order), and executes -their rig-generate code. All of the actual rig generation happens in the -rig-type python objects. - -And that's pretty much it. As you can see, most of the important code is -actually in the rig types themselves, not in Rigify. Rigify is pretty sparse -when it comes right down to it. - -There is one final stage to rig generation. Rigify checks all of the bones -for "DEF-", "MCH-", and "ORG-" prefixes, and moves those bones to their own -layers. It also sets all of the "DEF-" bones to deform, and sets all other -bones to _not_ deform. And finally, it looks for any bone that does not have -a parent, and sets the root bone (which Rigify creates) as their parent. - - -THE GUTS OF A RIG TYPE, BASIC ------------------------------ -A rig type is simply a python module containing a class named "Rig", and some -optional module functions. The Rig class has only two methods: -__init__() and generate() - -__init__() is the "information gathering" code for the rig type. When Rigify -loops through the bones and finds a tagged bone, it will create a python -object from the Rig class, executing this method. -In addition to the default "self" parameter, __init__() needs to take the -armature object, the name of the bone that was tagged, and a parameters object. - -A proper rig-type __init__() will look like this: - - def __init__(self, obj, bone_name, params): - # code goes here - -At the bare minimum, you are going to want to store the object and bone name -in the rig type object for later reference in the generate() method. So: - - def __init__(self, obj, bone_name, params): - self.obj = obj - self.org_bone = bone_name - -Most rig types involve more than just that one bone, though, so you will also -want to store the names of any other relevant bones. For example, maybe the -parent of the tagged bone is important to the rig type: - - def __init__(self, obj, bone_name, params): - self.obj = obj - self.org_bone = bone_name - self.org_parent = obj.data.bones[bone_name].parent.name - -It is important that you store the _names_ of the bones, and not direct -references. Due to the inner workings of Blender's armature system, direct -edit-bone and pose-bone references are lost when flipping in and out of -armature edit mode. (Arg...) - -Remember that it is critical that the information-gathering method does _not_ -modify the armature in any way. This way all of the rig type's info-gathering -methods can execute on a clean armature. Many rig types depend on traversing -parent-child relationships to figure out what bones are relevant to them, for -example. - - -Next is the generate() method. This is the method that Rigify calls to -actually generate the rig. It takes the form: - - def generate(self): - # code goes here - -It doesn't take any parameters beyond "self". So you have to store any -information you need with the __init__() method. - -generate() pretty much has free reign to do whatever it wants, with the exception -of two simple rules: -1. Other than the "ORG-" bones, do not touch anything that is not created by -the rig type (this prevents rig types from messing each other up). -2. Even with "ORG-" bones, the only thing you are allowed to do is add children -and add constraints. Do not rename them, do not remove children or -constraints, and especially do not change their parents. (Adding constraints -and adding children are encouraged, though. ;-)) This is because the "ORG-" -bones are the glue that holds everything together, and changing them beyond -adding children/constraints ruins the glue, so to speak. - -In short: with the exception of adding children/constraints to "ORG-" -bones, only mess with things that you yourself create. - -It is also generally a good idea (though not strictly required) that the rig -type add constraints to the "ORG-" bones it was generated from so that the -"ORG-" bones move with the animation controls. -For example, if I make a simple arm rig type, the controls that the animator -uses should also move the "ORG-" bones. That way, any other rig-types that are -children of those "ORG-" bones will move along with them. For example, any -fingers on the end of the arm. - -Also, any bones that the animator should not directly animate with should have -their names prefixed with "DEF-" or "MCH-". The former if it is a bone that -is intended to deform the mesh, the latter if it is not. -It should be obvious, then, that a bone cannot be both an animation control and -a deforming bone in Rigify. This is on purpose. - -Also note that there are convenience functions in utils.py for prepending -"DEF-" and "MCH-" to bone names: deformer() and mch() -There is also a convenience function for stripping "ORG-" from a bone name: -strip_org() -Which is useful for removing "ORG-" from bones you create by duplicating -the "ORG-" bones. -I recommend you use these functions instead of manually adding/stripping -these prefixes. That way if the prefixes are changed, it can be changed in -one place (those functions) and all the rig types will still work. - - -THE GUTS OF A RIG TYPE, ADVANCED --------------------------------- -If you look at any of the rig types included with Rigify, you'll note that they -have several functions outside of the Rig class. -THESE ADDITIONAL FUNCTIONS ARE _NOT_ REQUIRED for a rig type to function. But -they can add some nifty functionality to your rig. - -Here are the additional functions relevant to Rigify, with brief decriptions of -what they are for: - - -RIG PARAMETERS --------------- -For many rig types, it is handy for the user to be able to tweak how they are -generated. For example, the included biped arm rig allows the user to specify -the axis of rotation for the elbow. - -There are two functions necessary to give a rig type user-tweakable parameters: -add_parameters() -parameters_ui() - -add_parameters() takes an IDPropertyGroup as input, and adds its parameters -to that group as RNA properties. For example: - - def add_parameters(params): - params.toggle_param = bpy.props.BoolProperty(name="Test toggle:", default=False, description="Just a test, not really used for anything.") - -parameters_ui() recieves a Blender UILayout object and an IDPropertyGroup -containing the parameters added by add_parameters(). It creates a GUI in the -UILayout for the user to tweak those parameters. For example: - - def parameters_ui(layout, params): - r = layout.row() - r.prop(params, "toggle_param") - - -SAMPLE METARIG --------------- -It is a good idea for all rig types to have a sample metarig that the user can -add to their own metarig. This is what the create_sample() function is for. - -create_sample() takes the current armature object as input, and adds the bones -for its rig-type's metarig. For example: - - def create_sample(obj): - bpy.ops.object.mode_set(mode='EDIT') - arm = obj.data - - bone = arm.edit_bones.new('Bone') - bone.head[:] = 0.0000, 0.0000, 0.0000 - bone.tail[:] = 0.0000, 0.0000, 1.0000 - bone.roll = 0.0000 - bone.use_connect = False - - bpy.ops.object.mode_set(mode='OBJECT') - pbone = obj.pose.bones[bone] - pbone.rigify_type = 'copy' - pbone.rigify_parameters.add() - -Obviously, this isn't something that you generally want to hand-code, -especially with more complex samples. When in edit-mode on an armature, -there is a "Rigify Dev Tools" panel in the View3d tools panel containing a -button labeled "Encode Sample to Python". This button will generate the python -code for create_sample() from the armature you are editing. The generated code -appears in a text block called "metarig_sample.py" - -IMPLEMENTATION RIGS -------------------- -Starting from version 0.5 you can create a Rig class as an implementation of a wrapper class. -This happens for limb rigs for example, where super_limb is kind of a wrapper class for arm, leg and paws. -To declare a class as an implementation just declare an IMPLEMENTATION constant in the module and set it to True. -Implementation classes are shown in the metarig samples list and generate a sample if a proper create_sample function is implemented, but cannot be directly assigned as a rigify type. - -GENERATING A PYTHON UI ----------------------- -The generate() method can also, optionally, return python code as a single -string. This python code is added to the "rig properties" panel that gets -auto-generated along with the rig. This is useful for exposing things like -IK/FK switches in a nice way to the animator. - -The string must be returned in a list, e.g.: - -return ["my python code"] - -The reason it needs to be put in a list is to leave room for expanding the API -in the future, for returning additional information. - diff --git a/rigify/__init__.py b/rigify/__init__.py index 2b065092..b86b7038 100644 --- a/rigify/__init__.py +++ b/rigify/__init__.py @@ -286,24 +286,38 @@ def register(): IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName) IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type") - IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Rigify Advanced Generation", - description="Rigify Advanced Generation Parameters", + IDStore.rigify_advanced_generation = bpy.props.BoolProperty(name="Advanced Options", + description="Enables/disables advanced options for Rigify rig generation", default=False) + def update_mode(self, context): + if self.rigify_generate_mode == 'new': + self.rigify_force_widget_update = False + + IDStore.rigify_generate_mode = bpy.props.EnumProperty(name="Rigify Generate Rig Mode", + description="'Generate Rig' mode. In 'overwrite' mode the features of the target rig will be updated as defined by the metarig. In 'new' mode a new rig will be created as defined by the metarig. Current mode", + update=update_mode, + items=(('overwrite', 'overwrite', ''), + ('new', 'new', ''))) + IDStore.rigify_force_widget_update = bpy.props.BoolProperty(name="Force Widget Update", - description="Rigify Force Widget Update", + description="Forces Rigify to delete and rebuild all the rig widgets. if unset, only missing widgets will be created", default=False) IDStore.rigify_target_rigs = bpy.props.CollectionProperty(type=RigifyName) IDStore.rigify_target_rig = bpy.props.StringProperty(name="Rigify Target Rig", - description="The Rig, Generate will run upon", + description="Defines which rig to overwrite. If unset, a new one called 'rig' will be created.", default="") IDStore.rigify_rig_uis = bpy.props.CollectionProperty(type=RigifyName) IDStore.rigify_rig_ui = bpy.props.StringProperty(name="Rigify Target Rig UI", - description="The Rig UI to overwrite", + description="Defines the UI to overwrite. It should always be the same as the target rig. If unset, 'rig_ui.py' will be used", default="") + IDStore.rigify_rig_basename = bpy.props.StringProperty(name="Rigify Rig Name", + description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used", + default="") + if (ui and 'legacy' in str(ui)) or bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode: # update legacy on restart or reload bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode = True @@ -326,11 +340,13 @@ def unregister(): del IDStore.rigify_types del IDStore.rigify_active_type del IDStore.rigify_advanced_generation + del IDStore.rigify_generate_mode del IDStore.rigify_force_widget_update del IDStore.rigify_target_rig del IDStore.rigify_target_rigs del IDStore.rigify_rig_uis del IDStore.rigify_rig_ui + del IDStore.rigify_rig_basename bpy.utils.unregister_class(RigifyName) bpy.utils.unregister_class(RigifyParameters) diff --git a/rigify/generate.py b/rigify/generate.py index 1af872d6..01e5de28 100644 --- a/rigify/generate.py +++ b/rigify/generate.py @@ -81,15 +81,30 @@ def generate_rig(context, metarig): # object to generate the rig in. print("Fetch rig.") - name = id_store.rigify_target_rig or "rig" + rig_new_name = "" + rig_old_name = "" + if id_store.rigify_rig_basename: + rig_new_name = id_store.rigify_rig_basename + "_rig" - try: - obj = scene.objects[name] - except KeyError: - obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) + if id_store.rigify_generate_mode == 'overwrite': + name = id_store.rigify_target_rig or "rig" + try: + obj = scene.objects[name] + rig_old_name = name + obj.name = rig_new_name or name + except KeyError: + rig_old_name = name + name = rig_new_name or name + obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) + obj.draw_type = 'WIRE' + scene.objects.link(obj) + else: + name = rig_new_name or "rig" + obj = bpy.data.objects.new(name, bpy.data.armatures.new(name)) # in case name 'rig' exists it will be rig.001 obj.draw_type = 'WIRE' scene.objects.link(obj) + id_store.rigify_target_rig = obj.name obj.data.pose_position = 'POSE' # Get rid of anim data in case the rig already existed @@ -102,17 +117,22 @@ def generate_rig(context, metarig): scene.objects.active = obj # Remove wgts if force update is set - if "WGTS" in scene.objects and id_store.rigify_force_widget_update: + wgts_group_name = "WGTS_" + (rig_old_name or obj.name) + if wgts_group_name in scene.objects and id_store.rigify_force_widget_update: bpy.ops.object.select_all(action='DESELECT') for i, lyr in enumerate(WGT_LAYERS): if lyr: context.scene.layers[i] = True - for wgt in bpy.data.objects["WGTS"].children: + for wgt in bpy.data.objects[wgts_group_name].children: wgt.select = True bpy.ops.object.delete(use_global=False) for i, lyr in enumerate(WGT_LAYERS): if lyr: context.scene.layers[i] = False + if rig_old_name: + bpy.data.objects[wgts_group_name].name = "WGTS_" + obj.name + + wgts_group_name = "WGTS_" + obj.name # Remove all bones from the generated rig armature. bpy.ops.object.mode_set(mode='EDIT') @@ -281,7 +301,7 @@ def generate_rig(context, metarig): t.tick("Create root bone: ") # Create Group widget - wgts_group_name = "WGTS" + # wgts_group_name = "WGTS" if wgts_group_name not in scene.objects: if wgts_group_name in bpy.data.objects: bpy.data.objects[wgts_group_name].user_clear() @@ -291,6 +311,18 @@ def generate_rig(context, metarig): scene.objects.link(wgts_obj) wgts_obj.layers = WGT_LAYERS t.tick("Create main WGTS: ") + # + # if id_store.rigify_generate_mode == 'new': + # bpy.ops.object.select_all(action='DESELECT') + # for wgt in bpy.data.objects[wgts_group_name].children: + # wgt.select = True + # for i, lyr in enumerate(WGT_LAYERS): + # if lyr: + # context.scene.layers[i] = True + # bpy.ops.object.make_single_user(obdata=True) + # for i, lyr in enumerate(WGT_LAYERS): + # if lyr: + # context.scene.layers[i] = False #---------------------------------- try: @@ -410,7 +442,7 @@ def generate_rig(context, metarig): # Assign shapes to bones # Object's with name WGT- get used as that bone's shape. for bone in bones: - wgt_name = (WGT_PREFIX + obj.data.bones[bone].name)[:63] # Object names are limited to 63 characters... arg + wgt_name = (WGT_PREFIX + obj.name + '_' + obj.data.bones[bone].name)[:63] # Object names are limited to 63 characters... arg if wgt_name in context.scene.objects: # Weird temp thing because it won't let me index by object name for ob in context.scene.objects: @@ -439,13 +471,24 @@ def generate_rig(context, metarig): layer_layout += [(l.name, l.row)] # Generate the UI script - rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' + if id_store.rigify_generate_mode == 'overwrite': + rig_ui_name = id_store.rigify_rig_ui or 'rig_ui.py' + else: + rig_ui_name = 'rig_ui.py' - if rig_ui_name in bpy.data.texts.keys(): + if id_store.rigify_generate_mode == 'overwrite' and rig_ui_name in bpy.data.texts.keys(): script = bpy.data.texts[rig_ui_name] script.clear() else: script = bpy.data.texts.new("rig_ui.py") + + rig_ui_old_name = "" + if id_store.rigify_rig_basename: + rig_ui_old_name = script.name + script.name = id_store.rigify_rig_basename + "_rig_ui.py" + + id_store.rigify_rig_ui = script.name + script.write(UI_SLIDERS % rig_id) for s in ui_scripts: script.write("\n " + s.replace("\n", "\n ") + "\n") @@ -465,14 +508,15 @@ def generate_rig(context, metarig): # Add rig_ui to logic skip = False ctrls = obj.game.controllers + for c in ctrls: - if 'Python' in c.name and c.text.name == 'rig_ui.py': + if 'Python' in c.name and c.text.name == script.name: skip = True break if not skip: bpy.ops.logic.controller_add(type='PYTHON', object=obj.name) ctrl = obj.game.controllers[-1] - ctrl.text = bpy.data.texts['rig_ui.py'] + ctrl.text = bpy.data.texts[script.name] t.tick("The rest: ") diff --git a/rigify/ui.py b/rigify/ui.py index b4feb2b6..b35bda77 100644 --- a/rigify/ui.py +++ b/rigify/ui.py @@ -69,19 +69,33 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): if show_warning: layout.label(text=WARNING, icon='ERROR') - layout.operator("pose.rigify_generate", text="Generate Rig") - layout.prop(id_store, "rigify_advanced_generation") + layout.operator("pose.rigify_generate", text="Generate Rig", icon='POSE_HLT') + if id_store.rigify_advanced_generation: + icon = 'UNLOCKED' + else: + icon = 'LOCKED' + layout.prop(id_store, "rigify_advanced_generation", toggle=True, icon=icon) if id_store.rigify_advanced_generation: + row = layout.row(align=True) + row.prop(id_store, "rigify_generate_mode", expand=True) + main_row = layout.row(align=True).split(percentage=0.3) col1 = main_row.column() col2 = main_row.column() - col1.label(text="Target Rig") - col1.label(text="Target UI") + col1.label(text="Rig Name") + row = col1.row() + row.label(text="Target Rig") + row.enabled = (id_store.rigify_generate_mode == "overwrite") + row = col1.row() + row.label(text="Target UI") + row.enabled = (id_store.rigify_generate_mode == "overwrite") row = col2.row(align=True) + row.prop(id_store, "rigify_rig_basename", text="", icon="SORTALPHA") + row = col2.row(align=True) for i in range(0, len(id_store.rigify_target_rigs)): id_store.rigify_target_rigs.remove(0) @@ -92,6 +106,7 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): row.prop_search(id_store, "rigify_target_rig", id_store, "rigify_target_rigs", text="", icon='OUTLINER_OB_ARMATURE') + row.enabled = (id_store.rigify_generate_mode == "overwrite") for i in range(0, len(id_store.rigify_rig_uis)): id_store.rigify_rig_uis.remove(0) @@ -102,8 +117,12 @@ class DATA_PT_rigify_buttons(bpy.types.Panel): row = col2.row() row.prop_search(id_store, "rigify_rig_ui", id_store, "rigify_rig_uis", text="", icon='TEXT') + row.enabled = (id_store.rigify_generate_mode == "overwrite") - layout.prop(id_store, "rigify_force_widget_update") + row = layout.row() + row.prop(id_store, "rigify_force_widget_update") + if id_store.rigify_generate_mode == 'new': + row.enabled = False if show_update_metarig: layout.label(text="Some bones have old legacy rigify_type. Click to upgrade", icon='ERROR') diff --git a/rigify/utils.py b/rigify/utils.py index ee5856c7..a1a1dbec 100644 --- a/rigify/utils.py +++ b/rigify/utils.py @@ -412,8 +412,9 @@ def create_widget(rig, bone_name, bone_transform_name=None): if bone_transform_name is None: bone_transform_name = bone_name - obj_name = WGT_PREFIX + bone_name + obj_name = WGT_PREFIX + rig.name + '_' + bone_name scene = bpy.context.scene + id_store = bpy.context.window_manager # Check if it already exists in the scene if obj_name in scene.objects: @@ -437,8 +438,9 @@ def create_widget(rig, bone_name, bone_transform_name=None): # Move object to bone position and set layers obj_to_bone(obj, rig, bone_transform_name) - if 'WGTS' in bpy.data.objects.keys(): - obj.parent = bpy.data.objects['WGTS'] + wgts_group_name = 'WGTS_' + rig.name + if wgts_group_name in bpy.data.objects.keys(): + obj.parent = bpy.data.objects[wgts_group_name] obj.layers = WGT_LAYERS return obj -- cgit v1.2.3