[Bf-extensions-cvs] [3423174b] master: Rigify: redesign generate.py and introduce a base rig class.

Alexander Gavrilov noreply at git.blender.org
Sat Sep 14 08:34:18 CEST 2019


Commit: 3423174b37a0784dc12035ff3f2fb536835099e1
Author: Alexander Gavrilov
Date:   Sat Mar 30 22:00:55 2019 +0300
Branches: master
https://developer.blender.org/rBA3423174b37a0784dc12035ff3f2fb536835099e1

Rigify: redesign generate.py and introduce a base rig class.

The main goals are to provide an official way for rigs to
interact in a structured way, and to remove mode switching
within rigs.

This involves introducing a base class for rigs that holds
rig-to-rig and rig-to-bone references, converting the main
generator into a class and passing it to rigs, and splitting
the single generate method into multiple passes.

For backward compatibility, old rigs are automatically handled
via a wrapper that translates between old and new API.

In addition, a way to create objects that receive the generate
callbacks that aren't rigs is introduced via the GeneratorPlugin
class. The UI script generation code is converted into a plugin.

Making generic rig 'template' classes that are intended to be
subclassed in specific rigs involves splitting operations done
in each stage into multiple methods that can be overridden
separately. The main callback thus ends up simply calling a
sequence of other methods.

To make such code cleaner it's better to allow registering
those methods as new callbacks that would be automatically
called by the system. This can be done via decorators.

A new metaclass used for all rig and generate plugin classes
builds and validates a table of all decorated methods, and
allows calling them all together with the main callback.

A new way to switch parents for IK bones based on the new
features is introduced, and used in the existing limb rigs.

Reviewers: icappiello campbellbarton

Differential Revision: https://developer.blender.org/D4624

===================================================================

M	rigify/__init__.py
A	rigify/base_generate.py
A	rigify/base_rig.py
M	rigify/generate.py
M	rigify/rig_lists.py
M	rigify/rig_ui_template.py
M	rigify/rigs/basic/copy_chain.py
M	rigify/rigs/basic/super_copy.py
A	rigify/rigs/chain_rigs.py
M	rigify/rigs/experimental/super_chain.py
M	rigify/rigs/faces/super_face.py
M	rigify/rigs/limbs/arm.py
M	rigify/rigs/limbs/leg.py
M	rigify/rigs/limbs/super_limb.py
M	rigify/rigs/spines/super_spine.py
M	rigify/ui.py
M	rigify/utils/__init__.py
M	rigify/utils/animation.py
M	rigify/utils/bones.py
M	rigify/utils/errors.py
M	rigify/utils/layers.py
M	rigify/utils/mechanism.py
A	rigify/utils/metaclass.py
M	rigify/utils/misc.py
M	rigify/utils/naming.py
M	rigify/utils/rig.py
A	rigify/utils/switch_parent.py
M	rigify/utils/widgets_basic.py

===================================================================

diff --git a/rigify/__init__.py b/rigify/__init__.py
index 000100f4..0ca663a8 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -20,9 +20,9 @@
 
 bl_info = {
     "name": "Rigify",
-    "version": (0, 5, 1),
-    "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello",
-    "blender": (2, 80, 0),
+    "version": (0, 6, 0),
+    "author": "Nathan Vegdahl, Lucio Rossi, Ivan Cappiello, Alexander Gavrilov",
+    "blender": (2, 81, 0),
     "description": "Automatic rigging from building-block components",
     "location": "Armature properties, Bone properties, View3d tools panel, Armature Add menu",
     "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
@@ -32,14 +32,17 @@ bl_info = {
 
 if "bpy" in locals():
     import importlib
-    importlib.reload(generate)
-    importlib.reload(ui)
+    # Don't reload base_rig or base_generate, because it would break issubclass checks,
+    # unless _all_ modules with classes inheriting from BaseRig are also reloaded.
     importlib.reload(utils)
+    importlib.reload(rig_ui_template)
     importlib.reload(feature_set_list)
-    importlib.reload(metarig_menu)
     importlib.reload(rig_lists)
+    importlib.reload(generate)
+    importlib.reload(ui)
+    importlib.reload(metarig_menu)
 else:
-    from . import (utils, feature_set_list, rig_lists, generate, ui, metarig_menu)
+    from . import (utils, base_rig, base_generate, rig_ui_template, feature_set_list, rig_lists, generate, ui, metarig_menu)
 
 import bpy
 import sys
@@ -459,12 +462,6 @@ def register():
     IDStore.rigify_transfer_only_selected = BoolProperty(
         name="Transfer Only Selected",
         description="Transfer selected bones only", default=True)
-    IDStore.rigify_transfer_start_frame = IntProperty(
-        name="Start Frame",
-        description="First Frame to Transfer", default=0, min= 0)
-    IDStore.rigify_transfer_end_frame = IntProperty(
-        name="End Frame",
-        description="Last Frame to Transfer", default=0, min= 0)
 
     # Update legacy on restart or reload.
     if (ui and 'legacy' in str(ui)) or bpy.context.preferences.addons['rigify'].preferences.legacy_mode:
@@ -486,11 +483,14 @@ def register_rig_parameters():
                 pass
     else:
         for rig in rig_lists.rigs:
-            r = rig_lists.rigs[rig]['module']
+            rig_module = rig_lists.rigs[rig]['module']
+            rig_class = rig_module.Rig
+            r = rig_class if hasattr(rig_class, 'add_parameters') else rig_module
             try:
                 r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
-            except AttributeError:
-                pass
+            except Exception:
+                import traceback
+                traceback.print_exc()
 
 
 def unregister():
@@ -522,8 +522,6 @@ def unregister():
     del IDStore.rigify_rig_ui
     del IDStore.rigify_rig_basename
     del IDStore.rigify_transfer_only_selected
-    del IDStore.rigify_transfer_start_frame
-    del IDStore.rigify_transfer_end_frame
 
     # Classes.
     for cls in classes:
diff --git a/rigify/base_generate.py b/rigify/base_generate.py
new file mode 100644
index 00000000..790a0e1e
--- /dev/null
+++ b/rigify/base_generate.py
@@ -0,0 +1,433 @@
+#====================== BEGIN GPL LICENSE BLOCK ======================
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#======================= END GPL LICENSE BLOCK ========================
+
+# <pep8 compliant>
+
+import bpy
+import sys
+import traceback
+
+from .utils.errors import MetarigError, RaiseErrorMixin
+from .utils.naming import random_id
+from .utils.metaclass import SingletonPluginMetaclass
+from .utils.rig import list_bone_names_depth_first_sorted, get_rigify_type
+from .utils.misc import assign_parameters
+
+from . import base_rig
+
+
+#=============================================
+# Generator Plugin
+#=============================================
+
+
+class GeneratorPlugin(base_rig.GenerateCallbackHost, metaclass=SingletonPluginMetaclass):
+    """
+    Base class for generator plugins.
+
+    Generator plugins are per-Generator singleton utility
+    classes that receive the same stage callbacks as rigs.
+
+    Useful for building entities shared by multiple rigs
+    (e.g. the python script), or for making fire-and-forget
+    utilities that actually require multiple stages to
+    complete.
+
+    This will create only one instance per set of args:
+
+      instance = PluginClass(generator, ...init args)
+    """
+
+    priority = 0
+
+    def __init__(self, generator):
+        self.generator = generator
+        self.obj = generator.obj
+
+    def register_new_bone(self, new_name, old_name=None):
+        self.generator.bone_owners[new_name] = None
+
+
+#=============================================
+# Rig Substitution Mechanism
+#=============================================
+
+
+class SubstitutionRig(RaiseErrorMixin):
+    """A proxy rig that replaces itself with one or more different rigs."""
+
+    def __init__(self, generator, pose_bone):
+        self.generator = generator
+
+        self.obj = generator.obj
+        self.base_bone = pose_bone.name
+        self.params = pose_bone.rigify_parameters
+
+    def substitute(self):
+        # return [rig1, rig2...]
+        raise NotImplementedException()
+
+    # Utility methods
+    def register_new_bone(self, new_name, old_name=None):
+        pass
+
+    def get_params(self, bone_name):
+        return self.obj.pose.bones[bone_name].rigify_parameters
+
+    def assign_params(self, bone_name, param_dict=None, **params):
+        assign_parameters(self.get_params(bone_name), param_dict, **params)
+
+    def instantiate_rig(self, rig_class, bone_name):
+        if isinstance(rig_class, str):
+            rig_class = self.generator.find_rig_class(rig_class)
+
+        return self.generator.instantiate_rig(rig_class, self.obj.pose.bones[bone_name])
+
+
+#=============================================
+# Legacy Rig Wrapper
+#=============================================
+
+
+class LegacyRig(base_rig.BaseRig):
+    """Wrapper around legacy style rigs without a common base class"""
+
+    def __init__(self, generator, pose_bone, wrapped_class):
+        self.wrapped_rig = None
+        self.wrapped_class = wrapped_class
+
+        super().__init__(generator, pose_bone)
+
+    def find_org_bones(self, pose_bone):
+        bone_name = pose_bone.name
+
+        if not self.wrapped_rig:
+            self.wrapped_rig = self.wrapped_class(self.obj, self.base_bone, self.params)
+
+            # Switch back to OBJECT mode if the rig changed it
+            if self.obj.mode != 'OBJECT':
+                bpy.ops.object.mode_set(mode='OBJECT')
+
+        # Try to extract the main list of bones - old rigs often have it.
+        # This is not actually strictly necessary, so failing is OK.
+        if hasattr(self.wrapped_rig, 'org_bones'):
+            bones = self.wrapped_rig.org_bones
+            if isinstance(bones, list):
+                return bones
+
+        return [bone_name]
+
+    def generate_bones(self):
+        # Inject references into the rig if it won't cause conflict
+        if not hasattr(self.wrapped_rig, 'rigify_generator'):
+            self.wrapped_rig.rigify_generator = self.generator
+        if not hasattr(self.wrapped_rig, 'rigify_wrapper'):
+            self.wrapped_rig.rigify_wrapper = self
+
+        # Old rigs only have one generate method, so call it from
+        # generate_bones, which is the only stage allowed to add bones.
+        scripts = self.wrapped_rig.generate()
+
+        # Switch back to EDIT mode if the rig changed it
+        if self.obj.mode != 'EDIT':
+            bpy.ops.object.mode_set(mode='EDIT')
+
+        if isinstance(scripts, dict):
+            if 'script' in scripts:
+                self.script.add_panel_code(scripts['script'])
+            if 'imports' in scripts:
+                self.script.add_imports(scripts['imports'])
+            if 'utilities' in scripts:
+                self.script.add_utilities(scripts['utilities'])
+            if 'register' in scripts:
+                self.script.register_classes(scripts['register'])
+            if 'register_drivers' in scripts:
+                self.script.register_driver_functions(scripts['register_drivers'])
+            if 'register_props' in scripts:
+                for prop, val in scripts['register_props']:
+                    self.script.register_property(prop, val)
+            if 'noparent_bones' in scripts:
+                for bone_name in scripts['noparent_bones']:
+                    self.generator.disable_auto_parent(bone_name)
+        elif scripts is not None:
+            self.script.add_panel_code([scripts[0]])
+
+    def finalize(self):
+        if hasattr(self.wrapped_rig, 'glue'):
+            self.wrapped_rig.glue()
+
+            # Switch back to OBJECT mode if the rig changed it
+            if self.obj.mode != 'OBJECT':
+                bpy.ops.object.mode_set(mode='OBJECT')
+
+
+#=============================================
+# Base Generate Engine
+#=============================================
+
+
+class BaseGenerator:
+    """Base class for the main generator object. Contains rig and plugin management code."""
+
+    def __init__(self, context, metarig):
+        self.context = context
+        self.scene = context.scene
+        self.view_layer = context.view_layer
+        self.layer_collection = context.layer_collection
+        self.collection = self.laye

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list